Merge "Use device owner type to restrict a financed device owner"
diff --git a/Android.bp b/Android.bp
index 8bc5d7a..838f304 100644
--- a/Android.bp
+++ b/Android.bp
@@ -177,6 +177,7 @@
soong_config_variables: {
include_nonpublic_framework_api: {
static_libs: [
+ "framework-auxiliary.impl",
"framework-supplementalapi.impl",
],
},
diff --git a/ApiDocs.bp b/ApiDocs.bp
index da6a2f6..b5acfb2 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -80,6 +80,7 @@
":i18n.module.public.api{.public.stubs.source}",
":framework-appsearch-sources",
+ ":framework-auxiliary-sources",
":framework-connectivity-sources",
":framework-bluetooth-sources",
":framework-connectivity-tiramisu-updatable-sources",
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/Android.bp b/api/Android.bp
index d8727f9..3075d38 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -129,6 +129,7 @@
"i18n.module.public.api",
],
conditional_bootclasspath: [
+ "framework-auxiliary",
"framework-supplementalapi",
],
system_server_classpath: [
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/boot/Android.bp b/boot/Android.bp
index 3273f2c..8958d70 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -56,6 +56,10 @@
module: "art-bootclasspath-fragment",
},
{
+ apex: "com.android.auxiliary",
+ module: "com.android.auxiliary-bootclasspath-fragment",
+ },
+ {
apex: "com.android.conscrypt",
module: "com.android.conscrypt-bootclasspath-fragment",
},
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 c35f5eab..922ad16 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -2108,6 +2108,8 @@
field public static final int icon_frame = 16908350; // 0x102003e
field public static final int input = 16908297; // 0x1020009
field public static final int inputArea = 16908318; // 0x102001e
+ field public static final int inputExtractAccessories;
+ field public static final int inputExtractAction;
field public static final int inputExtractEditText = 16908325; // 0x1020025
field @Deprecated public static final int keyboardView = 16908326; // 0x1020026
field public static final int list = 16908298; // 0x102000a
@@ -4181,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);
@@ -4484,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>...);
@@ -31679,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();
@@ -31690,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
@@ -32414,6 +32420,7 @@
method @Nullable public android.os.storage.StorageVolume getStorageVolume(@NonNull java.io.File);
method @NonNull public android.os.storage.StorageVolume getStorageVolume(@NonNull android.net.Uri);
method @NonNull public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public java.util.List<android.os.storage.StorageVolume> getStorageVolumesIncludingSharedProfiles();
method @NonNull public java.util.UUID getUuidForPath(@NonNull java.io.File) throws java.io.IOException;
method public boolean isAllocationSupported(@NonNull java.io.FileDescriptor);
method public boolean isCacheBehaviorGroup(java.io.File) throws java.io.IOException;
@@ -32447,6 +32454,7 @@
method public String getDescription(android.content.Context);
method @Nullable public java.io.File getDirectory();
method @Nullable public String getMediaStoreVolumeName();
+ method @NonNull public android.os.UserHandle getOwner();
method public String getState();
method @Nullable public java.util.UUID getStorageUuid();
method @Nullable public String getUuid();
@@ -37924,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 {
@@ -37955,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();
}
@@ -38037,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);
@@ -38086,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();
@@ -49164,6 +49206,8 @@
public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable {
ctor public SurfaceControlViewHost.SurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
method public int describeContents();
+ method public void notifyConfigurationChanged(@NonNull android.content.res.Configuration);
+ method public void notifyDetachedFromWindow();
method public void release();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControlViewHost.SurfacePackage> CREATOR;
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 1f40e59..8ea1abc 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -2,6 +2,7 @@
package android {
public static final class Manifest.permission {
+ field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
}
@@ -162,6 +163,8 @@
public class LocationManager {
method @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public boolean injectLocation(@NonNull android.location.Location);
+ method @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS) public boolean isAutomotiveGnssSuspended();
+ method @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS) public void setAutomotiveGnssSuspended(boolean);
}
}
@@ -538,10 +541,6 @@
field public static final int APP_IO_BLOCKED_REASON_UNKNOWN = 0; // 0x0
}
- public final class StorageVolume implements android.os.Parcelable {
- method @NonNull public android.os.UserHandle getOwner();
- }
-
}
package android.provider {
@@ -556,6 +555,26 @@
field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
}
+ public static final class Settings.Global extends android.provider.Settings.NameValueTable {
+ field public static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled";
+ field public static final String BLE_SCAN_BACKGROUND_MODE = "ble_scan_background_mode";
+ field public static final String BLE_SCAN_BALANCED_INTERVAL_MS = "ble_scan_balanced_interval_ms";
+ field public static final String BLE_SCAN_BALANCED_WINDOW_MS = "ble_scan_balanced_window_ms";
+ field public static final String BLE_SCAN_LOW_LATENCY_INTERVAL_MS = "ble_scan_low_latency_interval_ms";
+ field public static final String BLE_SCAN_LOW_LATENCY_WINDOW_MS = "ble_scan_low_latency_window_ms";
+ field public static final String BLE_SCAN_LOW_POWER_INTERVAL_MS = "ble_scan_low_power_interval_ms";
+ field public static final String BLE_SCAN_LOW_POWER_WINDOW_MS = "ble_scan_low_power_window_ms";
+ field public static final String BLUETOOTH_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode";
+ field public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device";
+ field public static final String BLUETOOTH_DISABLED_PROFILES = "bluetooth_disabled_profiles";
+ }
+
+ public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
+ field public static final String BLUETOOTH_ADDRESS = "bluetooth_address";
+ field public static final String BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid";
+ field public static final String BLUETOOTH_NAME = "bluetooth_name";
+ }
+
}
package android.telephony {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6694303..8e68858 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -37,7 +37,6 @@
field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
field public static final String ASSOCIATE_COMPANION_DEVICES = "android.permission.ASSOCIATE_COMPANION_DEVICES";
- field public static final String AUTOMOTIVE_GNSS_CONTROLS = "android.permission.AUTOMOTIVE_GNSS_CONTROLS";
field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
field public static final String BACKUP = "android.permission.BACKUP";
field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION";
@@ -73,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";
@@ -168,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";
@@ -2112,6 +2113,7 @@
method @Nullable public android.net.Uri getSliceUri();
method @NonNull public String getSmartspaceTargetId();
method @Nullable public String getSourceNotificationKey();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData getTemplateData();
method @NonNull public android.os.UserHandle getUserHandle();
method @Nullable public android.appwidget.AppWidgetProviderInfo getWidget();
method public boolean isSensitive();
@@ -2142,6 +2144,14 @@
field public static final int FEATURE_UPCOMING_ALARM = 23; // 0x17
field public static final int FEATURE_WEATHER = 1; // 0x1
field public static final int FEATURE_WEATHER_ALERT = 10; // 0xa
+ field public static final int UI_TEMPLATE_CAROUSEL = 4; // 0x4
+ field public static final int UI_TEMPLATE_COMBINED_CARDS = 6; // 0x6
+ field public static final int UI_TEMPLATE_DEFAULT = 1; // 0x1
+ field public static final int UI_TEMPLATE_HEAD_TO_HEAD = 5; // 0x5
+ field public static final int UI_TEMPLATE_SUB_CARD = 7; // 0x7
+ field public static final int UI_TEMPLATE_SUB_IMAGE = 2; // 0x2
+ field public static final int UI_TEMPLATE_SUB_LIST = 3; // 0x3
+ field public static final int UI_TEMPLATE_UNDEFINED = 0; // 0x0
}
public static final class SmartspaceTarget.Builder {
@@ -2160,6 +2170,7 @@
method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setShouldShowExpanded(boolean);
method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSliceUri(@NonNull android.net.Uri);
method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSourceNotificationKey(@NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setTemplateData(@Nullable android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData);
method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setWidget(@NonNull android.appwidget.AppWidgetProviderInfo);
}
@@ -2188,6 +2199,177 @@
}
+package android.app.smartspace.uitemplatedata {
+
+ public final class SmartspaceCarouselUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getCarouselAction();
+ method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem> getCarouselItems();
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData> CREATOR;
+ }
+
+ public static final class SmartspaceCarouselUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceCarouselUiTemplateData.Builder(@NonNull java.util.List<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem>);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.Builder setCarouselAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ }
+
+ public static final class SmartspaceCarouselUiTemplateData.CarouselItem implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getImage();
+ method @Nullable public CharSequence getLowerText();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getTapAction();
+ method @Nullable public CharSequence getUpperText();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem> CREATOR;
+ }
+
+ public static final class SmartspaceCarouselUiTemplateData.CarouselItem.Builder {
+ ctor public SmartspaceCarouselUiTemplateData.CarouselItem.Builder();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setImage(@Nullable android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setLowerText(@Nullable CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setTapAction(@Nullable android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setUpperText(@Nullable CharSequence);
+ }
+
+ public final class SmartspaceCombinedCardsUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+ method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData> getCombinedCardDataList();
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceCombinedCardsUiTemplateData> CREATOR;
+ }
+
+ public static final class SmartspaceCombinedCardsUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceCombinedCardsUiTemplateData.Builder(@NonNull java.util.List<android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData>);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCombinedCardsUiTemplateData build();
+ }
+
+ public class SmartspaceDefaultUiTemplateData implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getPrimaryTapAction();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getSubTitleIcon();
+ method @Nullable public CharSequence getSubtitleText();
+ method @Nullable public CharSequence getSupplementalAlarmText();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getSupplementalSubtitleIcon();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSupplementalSubtitleTapAction();
+ method @Nullable public CharSequence getSupplementalSubtitleText();
+ method public int getTemplateType();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getTitleIcon();
+ method @Nullable public CharSequence getTitleText();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData> CREATOR;
+ }
+
+ public static class SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceDefaultUiTemplateData.Builder(int);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setPrimaryTapAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSubTitleIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSubtitleText(@NonNull CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalAlarmText(@NonNull CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalSubtitleIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalSubtitleTapAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalSubtitleText(@NonNull CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setTitleIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setTitleText(@NonNull CharSequence);
+ }
+
+ public final class SmartspaceHeadToHeadUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getHeadToHeadAction();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getHeadToHeadFirstCompetitorIcon();
+ method @Nullable public CharSequence getHeadToHeadFirstCompetitorText();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getHeadToHeadSecondCompetitorIcon();
+ method @Nullable public CharSequence getHeadToHeadSecondCompetitorText();
+ method @Nullable public CharSequence getHeadToHeadTitle();
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData> CREATOR;
+ }
+
+ public static final class SmartspaceHeadToHeadUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceHeadToHeadUiTemplateData.Builder();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadAction(@Nullable android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadFirstCompetitorIcon(@Nullable android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadFirstCompetitorText(@Nullable CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadSecondCompetitorIcon(@Nullable android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadSecondCompetitorText(@Nullable CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadTitle(@Nullable CharSequence);
+ }
+
+ public final class SmartspaceIcon implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public CharSequence getContentDescription();
+ method @NonNull public android.graphics.drawable.Icon getIcon();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceIcon> CREATOR;
+ }
+
+ public static final class SmartspaceIcon.Builder {
+ ctor public SmartspaceIcon.Builder(@NonNull android.graphics.drawable.Icon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceIcon build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceIcon.Builder setContentDescription(@NonNull CharSequence);
+ }
+
+ public final class SmartspaceSubCardUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSubCardAction();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceIcon getSubCardIcon();
+ method @Nullable public CharSequence getSubCardText();
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData> CREATOR;
+ }
+
+ public static final class SmartspaceSubCardUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceSubCardUiTemplateData.Builder(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData.Builder setSubCardAction(@NonNull CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData.Builder setSubCardAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ }
+
+ public final class SmartspaceSubImageUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSubImageAction();
+ method @NonNull public java.util.List<java.lang.CharSequence> getSubImageTexts();
+ method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.SmartspaceIcon> getSubImages();
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceSubImageUiTemplateData> CREATOR;
+ }
+
+ public static final class SmartspaceSubImageUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceSubImageUiTemplateData.Builder(@NonNull java.util.List<java.lang.CharSequence>, @NonNull java.util.List<android.app.smartspace.uitemplatedata.SmartspaceIcon>);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubImageUiTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubImageUiTemplateData.Builder setCarouselAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ }
+
+ public final class SmartspaceSubListUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData {
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSubListAction();
+ method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getSubListIcon();
+ method @NonNull public java.util.List<java.lang.CharSequence> getSubListTexts();
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData> CREATOR;
+ }
+
+ public static final class SmartspaceSubListUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder {
+ ctor public SmartspaceSubListUiTemplateData.Builder(@NonNull java.util.List<java.lang.CharSequence>);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData.Builder setCarouselAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData.Builder setSubListIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon);
+ }
+
+ public final class SmartspaceTapAction implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.os.Bundle getExtras();
+ method @Nullable public CharSequence getId();
+ method @Nullable public android.content.Intent getIntent();
+ method @Nullable public android.app.PendingIntent getPendingIntent();
+ method @Nullable public android.os.UserHandle getUserHandle();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceTapAction> CREATOR;
+ }
+
+ public static final class SmartspaceTapAction.Builder {
+ ctor public SmartspaceTapAction.Builder(@NonNull CharSequence);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction build();
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setIntent(@NonNull android.content.Intent);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setPendingIntent(@NonNull android.app.PendingIntent);
+ method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setUserHandle(@Nullable android.os.UserHandle);
+ }
+
+}
+
package android.app.time {
public final class Capabilities {
@@ -2357,12 +2539,21 @@
public class AppHibernationManager {
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.List<java.lang.String> getHibernatingPackagesForUser();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.Map<java.lang.String,android.apphibernation.HibernationStats> getHibernationStatsForUser(@NonNull java.util.Set<java.lang.String>);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.Map<java.lang.String,android.apphibernation.HibernationStats> getHibernationStatsForUser();
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingForUser(@NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingGlobally(@NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingForUser(@NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingGlobally(@NonNull String, boolean);
}
+ public final class HibernationStats implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getDiskBytesSaved();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.apphibernation.HibernationStats> CREATOR;
+ }
+
}
package android.companion {
@@ -2419,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);
@@ -2430,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>);
}
@@ -5363,7 +5558,6 @@
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.location.Location getLastKnownLocation(@NonNull String, @NonNull android.location.LastLocationRequest);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void injectGnssMeasurementCorrections(@NonNull android.location.GnssMeasurementCorrections);
method public boolean isAdasGnssLocationEnabled();
- method @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS) public boolean isAutoGnssSuspended();
method public boolean isExtraLocationControllerPackageEnabled();
method public boolean isLocationEnabledForUser(@NonNull android.os.UserHandle);
method public boolean isProviderEnabledForUser(@NonNull String, @NonNull android.os.UserHandle);
@@ -5376,7 +5570,6 @@
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAdasGnssLocationEnabled(boolean);
- method @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS) public void setAutoGnssSuspended(boolean);
method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackage(@Nullable String);
method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackageEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle);
@@ -6287,6 +6480,7 @@
method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig);
method @NonNull public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames(@NonNull String);
method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String);
+ method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPid(@NonNull String);
method public int getClientPriority(int, @Nullable String);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos();
method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
@@ -6459,7 +6653,7 @@
method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
- method @Nullable public android.media.tv.tuner.frontend.FrontendStatusReadiness[] getFrontendStatusReadiness(@NonNull int[]);
+ method @NonNull public java.util.List<android.media.tv.tuner.frontend.FrontendStatusReadiness> getFrontendStatusReadiness(@NonNull int[]);
method @IntRange(from=0xffffffff) public int getMaxNumberOfFrontends(int);
method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean hasUnusedFrontend(int);
method public boolean isLowestPriority(int);
@@ -7704,7 +7898,7 @@
method public boolean isLocked();
}
- public class FrontendStatusReadiness {
+ public final class FrontendStatusReadiness {
method public int getStatusReadiness();
method public int getStatusType();
field public static final int FRONTEND_STATUS_READINESS_STABLE = 3; // 0x3
@@ -9125,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);
@@ -10450,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 {
@@ -11004,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();
@@ -11242,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 f6280ad..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 {
@@ -583,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 {
@@ -1720,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 {
@@ -2361,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 38138d8..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)));
@@ -7161,11 +7224,6 @@
writer.println(mChangingConfigurations);
writer.print(innerPrefix); writer.print("mCurrentConfig=");
writer.println(mCurrentConfig);
- if (getResources().hasOverrideDisplayAdjustments()) {
- writer.print(innerPrefix);
- writer.print("FixedRotationAdjustments=");
- writer.println(getResources().getDisplayAdjustments().getFixedRotationAdjustments());
- }
mFragments.dumpLoaders(innerPrefix, fd, writer, args);
mFragments.getFragmentManager().dump(innerPrefix, fd, writer, args);
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 ea62714..876e401 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -175,7 +175,6 @@
import android.view.Choreographer;
import android.view.Display;
import android.view.DisplayAdjustments;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.View;
@@ -201,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;
@@ -598,12 +598,6 @@
/** The options for scene transition. */
ActivityOptions mActivityOptions;
- /**
- * If non-null, the activity is launching with a specified rotation, the adjustments should
- * be consumed before activity creation.
- */
- FixedRotationAdjustments mPendingFixedRotationAdjustments;
-
/** Whether this activiy was launched from a bubble. */
boolean mLaunchedFromBubble;
@@ -625,8 +619,7 @@
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
- IBinder assistToken, FixedRotationAdjustments fixedRotationAdjustments,
- IBinder shareableActivityToken, boolean launchedFromBubble) {
+ IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble) {
this.token = token;
this.assistToken = assistToken;
this.shareableActivityToken = shareableActivityToken;
@@ -646,7 +639,6 @@
this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo,
compatInfo);
mActivityOptions = activityOptions;
- mPendingFixedRotationAdjustments = fixedRotationAdjustments;
mLaunchedFromBubble = launchedFromBubble;
init();
}
@@ -3512,21 +3504,6 @@
mH.sendMessage(msg);
}
- private void sendMessage(int what, Object obj, int arg1, int arg2, int seq) {
- if (DEBUG_MESSAGES) Slog.v(
- TAG, "SCHEDULE " + mH.codeToString(what) + " arg1=" + arg1 + " arg2=" + arg2 +
- "seq= " + seq);
- Message msg = Message.obtain();
- msg.what = what;
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = obj;
- args.argi1 = arg1;
- args.argi2 = arg2;
- args.argi3 = seq;
- msg.obj = args;
- mH.sendMessage(msg);
- }
-
final void scheduleContextCleanup(ContextImpl context, String who,
String what) {
ContextCleanupInfo cci = new ContextCleanupInfo();
@@ -3536,64 +3513,6 @@
sendMessage(H.CLEAN_UP_CONTEXT, cci);
}
- /**
- * Applies the rotation adjustments to override display information in resources belong to the
- * provided token. If the token is activity token, the adjustments also apply to application
- * because the appearance of activity is usually more sensitive to the application resources.
- *
- * @param token The token to apply the adjustments.
- * @param fixedRotationAdjustments The information to override the display adjustments of
- * corresponding resources. If it is null, the exiting override
- * will be cleared.
- */
- @Override
- public void handleFixedRotationAdjustments(@NonNull IBinder token,
- @Nullable FixedRotationAdjustments fixedRotationAdjustments) {
- final Consumer<DisplayAdjustments> override = fixedRotationAdjustments != null
- ? displayAdjustments -> displayAdjustments
- .setFixedRotationAdjustments(fixedRotationAdjustments)
- : null;
- if (!mResourcesManager.overrideTokenDisplayAdjustments(token, override)) {
- // No resources are associated with the token.
- return;
- }
- if (mActivities.get(token) == null) {
- // Nothing to do for application if it is not an activity token.
- return;
- }
-
- overrideApplicationDisplayAdjustments(token, override);
- }
-
- /**
- * Applies the last override to application resources for compatibility. Because the Resources
- * of Display can be from application, e.g.
- * applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId)
- * and the deprecated usage:
- * applicationContext.getSystemService(WindowManager.class).getDefaultDisplay();
- *
- * @param token The owner and target of the override.
- * @param override The display adjustments override for application resources. If it is null,
- * the override of the token will be removed and pop the last one to use.
- */
- private void overrideApplicationDisplayAdjustments(@NonNull IBinder token,
- @Nullable Consumer<DisplayAdjustments> override) {
- final Consumer<DisplayAdjustments> appOverride;
- if (mActiveRotationAdjustments == null) {
- mActiveRotationAdjustments = new ArrayList<>(2);
- }
- if (override != null) {
- mActiveRotationAdjustments.add(Pair.create(token, override));
- appOverride = override;
- } else {
- mActiveRotationAdjustments.removeIf(adjustmentsPair -> adjustmentsPair.first == token);
- appOverride = mActiveRotationAdjustments.isEmpty()
- ? null
- : mActiveRotationAdjustments.get(mActiveRotationAdjustments.size() - 1).second;
- }
- mInitialApplication.getResources().overrideDisplayAdjustments(appOverride);
- }
-
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
@@ -3811,19 +3730,6 @@
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
- // The rotation adjustments must be applied before creating the activity, so the activity
- // can get the adjusted display info during creation.
- if (r.mPendingFixedRotationAdjustments != null) {
- // The adjustments should have been set by handleLaunchActivity, so the last one is the
- // override for activity resources.
- if (mActiveRotationAdjustments != null && !mActiveRotationAdjustments.isEmpty()) {
- mResourcesManager.overrideTokenDisplayAdjustments(r.token,
- mActiveRotationAdjustments.get(
- mActiveRotationAdjustments.size() - 1).second);
- }
- r.mPendingFixedRotationAdjustments = null;
- }
-
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
@@ -3859,13 +3765,6 @@
mProfiler.startProfiling();
}
- if (r.mPendingFixedRotationAdjustments != null) {
- // The rotation adjustments must be applied before handling configuration, so process
- // level display metrics can be adjusted.
- overrideApplicationDisplayAdjustments(r.token, adjustments ->
- adjustments.setFixedRotationAdjustments(r.mPendingFixedRotationAdjustments));
- }
-
// Make sure we are running with the most recent config.
mConfigurationController.handleConfigurationChanged(null, null);
@@ -4693,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 {
@@ -5974,13 +5873,6 @@
final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull(
amOverrideConfig, contextThemeWrapperOverrideConfig);
mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, displayId);
- final Resources res = activity.getResources();
- if (res.hasOverrideDisplayAdjustments()) {
- // If fixed rotation is applied while the activity is visible (e.g. PiP), the rotated
- // configuration of activity may be sent later than the adjustments. In this case, the
- // adjustments need to be updated for the consistency of display info.
- res.getDisplayAdjustments().getConfiguration().updateFrom(finalOverrideConfig);
- }
activity.mConfigChangeFlags = 0;
activity.mCurrentConfig = new Configuration(newConfig);
@@ -7643,8 +7535,7 @@
// We need to apply this change to the resources immediately, because upon returning
// the view hierarchy will be informed about it.
if (mResourcesManager.applyConfigurationToResources(globalConfig,
- null /* compat */,
- mInitialApplication.getResources().getDisplayAdjustments())) {
+ null /* compat */)) {
mConfigurationController.updateLocaleListFromAppContext(
mInitialApplication.getApplicationContext());
@@ -7922,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/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index d365269..65e6ab7 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -27,7 +27,6 @@
import android.content.res.Configuration;
import android.os.IBinder;
import android.util.MergedConfiguration;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import android.view.SurfaceControl;
import android.window.SplashScreenView.SplashScreenViewParcelable;
@@ -187,10 +186,6 @@
/** Deliver app configuration change notification. */
public abstract void handleConfigurationChanged(Configuration config);
- /** Apply addition adjustments to override display information. */
- public abstract void handleFixedRotationAdjustments(IBinder token,
- FixedRotationAdjustments fixedRotationAdjustments);
-
/**
* Get {@link android.app.ActivityThread.ActivityClientRecord} instance that corresponds to the
* provided token.
diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java
index 58f60a6..1a77b65 100644
--- a/core/java/android/app/ConfigurationController.java
+++ b/core/java/android/app/ConfigurationController.java
@@ -185,14 +185,7 @@
final Application app = mActivityThread.getApplication();
final Resources appResources = app.getResources();
- if (appResources.hasOverrideDisplayAdjustments()) {
- // The value of Display#getRealSize will be adjusted by FixedRotationAdjustments,
- // but Display#getSize refers to DisplayAdjustments#mConfiguration. So the rotated
- // configuration also needs to set to the adjustments for consistency.
- appResources.getDisplayAdjustments().getConfiguration().updateFrom(config);
- }
- mResourcesManager.applyConfigurationToResources(config, compat,
- appResources.getDisplayAdjustments());
+ mResourcesManager.applyConfigurationToResources(config, compat);
updateLocaleListFromAppContext(app.getApplicationContext());
if (mConfiguration == null) {
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/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index f360bbed..2a1883d 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -65,7 +65,6 @@
import java.util.List;
import java.util.Objects;
import java.util.WeakHashMap;
-import java.util.function.Consumer;
import java.util.function.Function;
/** @hide */
@@ -343,6 +342,25 @@
return dm;
}
+ /**
+ * Like getDisplayMetrics, but will adjust the result based on the display information in
+ * config. This is used to make sure that the global configuration matches the activity's
+ * apparent display.
+ */
+ private DisplayMetrics getDisplayMetrics(Configuration config) {
+ final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
+ final DisplayMetrics dm = new DisplayMetrics();
+ final DisplayInfo displayInfo = displayManagerGlobal != null
+ ? displayManagerGlobal.getDisplayInfo(mResDisplayId) : null;
+ if (displayInfo != null) {
+ displayInfo.getAppMetrics(dm,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS.getCompatibilityInfo(), config);
+ } else {
+ dm.setToDefaults();
+ }
+ return dm;
+ }
+
private static void applyDisplayMetricsToConfiguration(@NonNull DisplayMetrics dm,
@NonNull Configuration config) {
config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
@@ -1311,12 +1329,6 @@
public final boolean applyConfigurationToResources(@NonNull Configuration config,
@Nullable CompatibilityInfo compat) {
- return applyConfigurationToResources(config, compat, null /* adjustments */);
- }
-
- /** Applies the global configuration to the managed resources. */
- public final boolean applyConfigurationToResources(@NonNull Configuration config,
- @Nullable CompatibilityInfo compat, @Nullable DisplayAdjustments adjustments) {
synchronized (mLock) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
@@ -1346,12 +1358,7 @@
applyAllPendingAppInfoUpdates();
}
- DisplayMetrics displayMetrics = getDisplayMetrics();
- if (adjustments != null) {
- // Currently the only case where the adjustment takes effect is to simulate
- // placing an app in a rotated display.
- adjustments.adjustGlobalAppMetrics(displayMetrics);
- }
+ final DisplayMetrics displayMetrics = getDisplayMetrics(config);
Resources.updateSystemConfiguration(config, displayMetrics, compat);
ApplicationPackageManager.configurationChanged();
@@ -1591,41 +1598,6 @@
}
}
- /**
- * Overrides the display adjustments of all resources which are associated with the given token.
- *
- * @param token The token that owns the resources.
- * @param override The operation to override the existing display adjustments. If it is null,
- * the override adjustments will be cleared.
- * @return {@code true} if the override takes effect.
- */
- public boolean overrideTokenDisplayAdjustments(IBinder token,
- @Nullable Consumer<DisplayAdjustments> override) {
- boolean handled = false;
- synchronized (mLock) {
- final ActivityResources tokenResources = mActivityResourceReferences.get(token);
- if (tokenResources == null) {
- return false;
- }
- final ArrayList<ActivityResource> resourcesRefs = tokenResources.activityResources;
- for (int i = resourcesRefs.size() - 1; i >= 0; i--) {
- final ActivityResource activityResource = resourcesRefs.get(i);
- if (activityResource.overrideDisplayId != null) {
- // This resource overrides the display of the token so we should not be
- // modifying its display adjustments here.
- continue;
- }
-
- final Resources res = activityResource.resources.get();
- if (res != null) {
- res.overrideDisplayAdjustments(override);
- handled = true;
- }
- }
- }
- return handled;
- }
-
private class UpdateHandler implements Resources.UpdateCallbacks {
/**
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/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 4ff7924..b791f05 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -37,6 +37,7 @@
import android.util.proto.ProtoOutputStream;
import android.util.proto.WireTypeMismatchException;
import android.view.DisplayInfo;
+import android.view.Surface;
import android.view.WindowManager;
import java.io.IOException;
@@ -74,6 +75,13 @@
private final Rect mMaxBounds = new Rect();
/**
+ * The rotation of this window's apparent display. This can differ from mRotation in some
+ * situations (like letterbox).
+ */
+ @Surface.Rotation
+ private int mDisplayRotation = ROTATION_UNDEFINED;
+
+ /**
* The current rotation of this window container relative to the default
* orientation of the display it is on (regardless of how deep in the hierarchy
* it is). It is used by the configuration hierarchy to apply rotation-dependent
@@ -196,6 +204,9 @@
/** Bit that indicates that the {@link #mDisplayWindowingMode} changed.
* @hide */
public static final int WINDOW_CONFIG_DISPLAY_WINDOWING_MODE = 1 << 7;
+ /** Bit that indicates that the apparent-display changed.
+ * @hide */
+ public static final int WINDOW_CONFIG_DISPLAY_ROTATION = 1 << 8;
/** @hide */
@IntDef(flag = true, prefix = { "WINDOW_CONFIG_" }, value = {
@@ -207,6 +218,7 @@
WINDOW_CONFIG_ALWAYS_ON_TOP,
WINDOW_CONFIG_ROTATION,
WINDOW_CONFIG_DISPLAY_WINDOWING_MODE,
+ WINDOW_CONFIG_DISPLAY_ROTATION,
})
public @interface WindowConfig {}
@@ -237,6 +249,7 @@
dest.writeInt(mAlwaysOnTop);
dest.writeInt(mRotation);
dest.writeInt(mDisplayWindowingMode);
+ dest.writeInt(mDisplayRotation);
}
/** @hide */
@@ -249,6 +262,7 @@
mAlwaysOnTop = source.readInt();
mRotation = source.readInt();
mDisplayWindowingMode = source.readInt();
+ mDisplayRotation = source.readInt();
}
@Override
@@ -318,6 +332,14 @@
}
/**
+ * Sets the apparent display cutout.
+ * @hide
+ */
+ public void setDisplayRotation(@Surface.Rotation int rotation) {
+ mDisplayRotation = rotation;
+ }
+
+ /**
* Sets whether this window should be always on top.
* @param alwaysOnTop {@code true} to set window always on top, otherwise {@code false}
* @hide
@@ -359,6 +381,14 @@
return mMaxBounds;
}
+ /**
+ * @see #setDisplayRotation
+ * @hide
+ */
+ public @Surface.Rotation int getDisplayRotation() {
+ return mDisplayRotation;
+ }
+
public int getRotation() {
return mRotation;
}
@@ -413,6 +443,7 @@
setBounds(other.mBounds);
setAppBounds(other.mAppBounds);
setMaxBounds(other.mMaxBounds);
+ setDisplayRotation(other.mDisplayRotation);
setWindowingMode(other.mWindowingMode);
setActivityType(other.mActivityType);
setAlwaysOnTop(other.mAlwaysOnTop);
@@ -431,6 +462,7 @@
setAppBounds(null);
setBounds(null);
setMaxBounds(null);
+ setDisplayRotation(ROTATION_UNDEFINED);
setWindowingMode(WINDOWING_MODE_UNDEFINED);
setActivityType(ACTIVITY_TYPE_UNDEFINED);
setAlwaysOnTop(ALWAYS_ON_TOP_UNDEFINED);
@@ -485,6 +517,11 @@
changed |= WINDOW_CONFIG_DISPLAY_WINDOWING_MODE;
setDisplayWindowingMode(delta.mDisplayWindowingMode);
}
+ if (delta.mDisplayRotation != ROTATION_UNDEFINED
+ && delta.mDisplayRotation != mDisplayRotation) {
+ changed |= WINDOW_CONFIG_DISPLAY_ROTATION;
+ setDisplayRotation(delta.mDisplayRotation);
+ }
return changed;
}
@@ -517,6 +554,9 @@
if ((mask & WINDOW_CONFIG_DISPLAY_WINDOWING_MODE) != 0) {
setDisplayWindowingMode(delta.mDisplayWindowingMode);
}
+ if ((mask & WINDOW_CONFIG_DISPLAY_ROTATION) != 0) {
+ setDisplayRotation(delta.mDisplayRotation);
+ }
}
/**
@@ -573,6 +613,11 @@
changes |= WINDOW_CONFIG_DISPLAY_WINDOWING_MODE;
}
+ if ((compareUndefined || other.mDisplayRotation != ROTATION_UNDEFINED)
+ && mDisplayRotation != other.mDisplayRotation) {
+ changes |= WINDOW_CONFIG_DISPLAY_ROTATION;
+ }
+
return changes;
}
@@ -620,8 +665,11 @@
if (n != 0) return n;
n = mRotation - that.mRotation;
if (n != 0) return n;
+
n = mDisplayWindowingMode - that.mDisplayWindowingMode;
if (n != 0) return n;
+ n = mDisplayRotation - that.mDisplayRotation;
+ if (n != 0) return n;
// if (n != 0) return n;
return n;
@@ -650,6 +698,7 @@
result = 31 * result + mAlwaysOnTop;
result = 31 * result + mRotation;
result = 31 * result + mDisplayWindowingMode;
+ result = 31 * result + mDisplayRotation;
return result;
}
@@ -659,6 +708,8 @@
return "{ mBounds=" + mBounds
+ " mAppBounds=" + mAppBounds
+ " mMaxBounds=" + mMaxBounds
+ + " mDisplayRotation=" + (mRotation == ROTATION_UNDEFINED
+ ? "undefined" : rotationToString(mDisplayRotation))
+ " mWindowingMode=" + windowingModeToString(mWindowingMode)
+ " mDisplayWindowingMode=" + windowingModeToString(mDisplayWindowingMode)
+ " mActivityType=" + activityTypeToString(mActivityType)
diff --git a/core/java/android/app/servertransaction/FixedRotationAdjustmentsItem.java b/core/java/android/app/servertransaction/FixedRotationAdjustmentsItem.java
deleted file mode 100644
index 4c06333..0000000
--- a/core/java/android/app/servertransaction/FixedRotationAdjustmentsItem.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-import android.annotation.Nullable;
-import android.app.ClientTransactionHandler;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
-
-import java.util.Objects;
-
-/**
- * The request to update display adjustments for a rotated activity or window token.
- * @hide
- */
-public class FixedRotationAdjustmentsItem extends ClientTransactionItem {
-
- /** The token who may have {@link android.content.res.Resources}. */
- private IBinder mToken;
-
- /**
- * The adjustments for the display adjustments of resources. If it is null, the existing
- * rotation adjustments will be dropped to restore natural state.
- */
- private FixedRotationAdjustments mFixedRotationAdjustments;
-
- private FixedRotationAdjustmentsItem() {}
-
- /** Obtain an instance initialized with provided params. */
- public static FixedRotationAdjustmentsItem obtain(IBinder token,
- FixedRotationAdjustments fixedRotationAdjustments) {
- FixedRotationAdjustmentsItem instance =
- ObjectPool.obtain(FixedRotationAdjustmentsItem.class);
- if (instance == null) {
- instance = new FixedRotationAdjustmentsItem();
- }
- instance.mToken = token;
- instance.mFixedRotationAdjustments = fixedRotationAdjustments;
-
- return instance;
- }
-
- @Override
- public void execute(ClientTransactionHandler client, IBinder token,
- PendingTransactionActions pendingActions) {
- client.handleFixedRotationAdjustments(mToken, mFixedRotationAdjustments);
- }
-
- @Override
- public void recycle() {
- mToken = null;
- mFixedRotationAdjustments = null;
- ObjectPool.recycle(this);
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeStrongBinder(mToken);
- dest.writeTypedObject(mFixedRotationAdjustments, flags);
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- final FixedRotationAdjustmentsItem other = (FixedRotationAdjustmentsItem) o;
- return Objects.equals(mToken, other.mToken)
- && Objects.equals(mFixedRotationAdjustments, other.mFixedRotationAdjustments);
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + Objects.hashCode(mToken);
- result = 31 * result + Objects.hashCode(mFixedRotationAdjustments);
- return result;
- }
-
- private FixedRotationAdjustmentsItem(Parcel in) {
- mToken = in.readStrongBinder();
- mFixedRotationAdjustments = in.readTypedObject(FixedRotationAdjustments.CREATOR);
- }
-
- public static final Creator<FixedRotationAdjustmentsItem> CREATOR =
- new Creator<FixedRotationAdjustmentsItem>() {
- public FixedRotationAdjustmentsItem createFromParcel(Parcel in) {
- return new FixedRotationAdjustmentsItem(in);
- }
-
- public FixedRotationAdjustmentsItem[] newArray(int size) {
- return new FixedRotationAdjustmentsItem[size];
- }
- };
-}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 0a2503c..abf1058 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -38,7 +38,6 @@
import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.Trace;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
@@ -78,7 +77,6 @@
* optimization for quick look up of the interface so the field is ignored for comparison.
*/
private IActivityClientController mActivityClientController;
- private FixedRotationAdjustments mFixedRotationAdjustments;
@Override
public void preExecute(ClientTransactionHandler client, IBinder token) {
@@ -97,8 +95,7 @@
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
- client, mAssistToken, mFixedRotationAdjustments, mShareableActivityToken,
- mLaunchedFromBubble);
+ client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble);
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -121,8 +118,7 @@
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken,
- IActivityClientController activityClientController,
- FixedRotationAdjustments fixedRotationAdjustments, IBinder shareableActivityToken,
+ IActivityClientController activityClientController, IBinder shareableActivityToken,
boolean launchedFromBubble) {
LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
if (instance == null) {
@@ -131,7 +127,7 @@
setValues(instance, intent, ident, info, curConfig, overrideConfig, compatInfo, referrer,
voiceInteractor, procState, state, persistentState, pendingResults,
pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken,
- activityClientController, fixedRotationAdjustments, shareableActivityToken,
+ activityClientController, shareableActivityToken,
launchedFromBubble);
return instance;
@@ -140,7 +136,7 @@
@Override
public void recycle() {
setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null,
- null, false, null, null, null, null, null, false);
+ null, false, null, null, null, null, false);
ObjectPool.recycle(this);
}
@@ -168,7 +164,6 @@
dest.writeTypedObject(mProfilerInfo, flags);
dest.writeStrongBinder(mAssistToken);
dest.writeStrongInterface(mActivityClientController);
- dest.writeTypedObject(mFixedRotationAdjustments, flags);
dest.writeStrongBinder(mShareableActivityToken);
dest.writeBoolean(mLaunchedFromBubble);
}
@@ -188,7 +183,7 @@
in.readTypedObject(ProfilerInfo.CREATOR),
in.readStrongBinder(),
IActivityClientController.Stub.asInterface(in.readStrongBinder()),
- in.readTypedObject(FixedRotationAdjustments.CREATOR), in.readStrongBinder(),
+ in.readStrongBinder(),
in.readBoolean());
}
@@ -232,7 +227,6 @@
&& mIsForward == other.mIsForward
&& Objects.equals(mProfilerInfo, other.mProfilerInfo)
&& Objects.equals(mAssistToken, other.mAssistToken)
- && Objects.equals(mFixedRotationAdjustments, other.mFixedRotationAdjustments)
&& Objects.equals(mShareableActivityToken, other.mShareableActivityToken);
}
@@ -254,7 +248,6 @@
result = 31 * result + (mIsForward ? 1 : 0);
result = 31 * result + Objects.hashCode(mProfilerInfo);
result = 31 * result + Objects.hashCode(mAssistToken);
- result = 31 * result + Objects.hashCode(mFixedRotationAdjustments);
result = 31 * result + Objects.hashCode(mShareableActivityToken);
return result;
}
@@ -292,7 +285,6 @@
+ ",persistentState=" + mPersistentState + ",pendingResults=" + mPendingResults
+ ",pendingNewIntents=" + mPendingNewIntents + ",options=" + mActivityOptions
+ ",profilerInfo=" + mProfilerInfo + ",assistToken=" + mAssistToken
- + ",rotationAdj=" + mFixedRotationAdjustments
+ ",shareableActivityToken=" + mShareableActivityToken + "}";
}
@@ -304,8 +296,7 @@
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo,
IBinder assistToken, IActivityClientController activityClientController,
- FixedRotationAdjustments fixedRotationAdjustments, IBinder shareableActivityToken,
- boolean launchedFromBubble) {
+ IBinder shareableActivityToken, boolean launchedFromBubble) {
instance.mIntent = intent;
instance.mIdent = ident;
instance.mInfo = info;
@@ -324,7 +315,6 @@
instance.mProfilerInfo = profilerInfo;
instance.mAssistToken = assistToken;
instance.mActivityClientController = activityClientController;
- instance.mFixedRotationAdjustments = fixedRotationAdjustments;
instance.mShareableActivityToken = shareableActivityToken;
instance.mLaunchedFromBubble = launchedFromBubble;
}
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
index 8e98535..78f51be 100644
--- a/core/java/android/app/smartspace/SmartspaceTarget.java
+++ b/core/java/android/app/smartspace/SmartspaceTarget.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.net.Uri;
@@ -131,6 +132,9 @@
@Nullable
private final AppWidgetProviderInfo mWidget;
+ @Nullable
+ private final SmartspaceDefaultUiTemplateData mTemplateData;
+
public static final int FEATURE_UNDEFINED = 0;
public static final int FEATURE_WEATHER = 1;
public static final int FEATURE_CALENDAR = 2;
@@ -189,6 +193,32 @@
public @interface FeatureType {
}
+ public static final int UI_TEMPLATE_UNDEFINED = 0;
+ public static final int UI_TEMPLATE_DEFAULT = 1;
+ public static final int UI_TEMPLATE_SUB_IMAGE = 2;
+ public static final int UI_TEMPLATE_SUB_LIST = 3;
+ public static final int UI_TEMPLATE_CAROUSEL = 4;
+ public static final int UI_TEMPLATE_HEAD_TO_HEAD = 5;
+ public static final int UI_TEMPLATE_COMBINED_CARDS = 6;
+ public static final int UI_TEMPLATE_SUB_CARD = 7;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"UI_TEMPLATE_"}, value = {
+ UI_TEMPLATE_UNDEFINED,
+ UI_TEMPLATE_DEFAULT,
+ UI_TEMPLATE_SUB_IMAGE,
+ UI_TEMPLATE_SUB_LIST,
+ UI_TEMPLATE_CAROUSEL,
+ UI_TEMPLATE_HEAD_TO_HEAD,
+ UI_TEMPLATE_COMBINED_CARDS,
+ UI_TEMPLATE_SUB_CARD
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UiTemplateType {
+ }
+
private SmartspaceTarget(Parcel in) {
this.mSmartspaceTargetId = in.readString();
this.mHeaderAction = in.readTypedObject(SmartspaceAction.CREATOR);
@@ -207,6 +237,7 @@
this.mAssociatedSmartspaceTargetId = in.readString();
this.mSliceUri = in.readTypedObject(Uri.CREATOR);
this.mWidget = in.readTypedObject(AppWidgetProviderInfo.CREATOR);
+ this.mTemplateData = in.readTypedObject(SmartspaceDefaultUiTemplateData.CREATOR);
}
private SmartspaceTarget(String smartspaceTargetId,
@@ -217,7 +248,7 @@
boolean shouldShowExpanded, String sourceNotificationKey,
ComponentName componentName, UserHandle userHandle,
String associatedSmartspaceTargetId, Uri sliceUri,
- AppWidgetProviderInfo widget) {
+ AppWidgetProviderInfo widget, SmartspaceDefaultUiTemplateData templateData) {
mSmartspaceTargetId = smartspaceTargetId;
mHeaderAction = headerAction;
mBaseAction = baseAction;
@@ -235,6 +266,7 @@
mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
mSliceUri = sliceUri;
mWidget = widget;
+ mTemplateData = templateData;
}
/**
@@ -371,6 +403,14 @@
}
/**
+ * Returns the UI template data.
+ */
+ @Nullable
+ public SmartspaceDefaultUiTemplateData getTemplateData() {
+ return mTemplateData;
+ }
+
+ /**
* @see Parcelable.Creator
*/
@NonNull
@@ -405,6 +445,7 @@
dest.writeString(this.mAssociatedSmartspaceTargetId);
dest.writeTypedObject(this.mSliceUri, flags);
dest.writeTypedObject(this.mWidget, flags);
+ dest.writeTypedObject(this.mTemplateData, flags);
}
@Override
@@ -432,6 +473,7 @@
+ ", mAssociatedSmartspaceTargetId='" + mAssociatedSmartspaceTargetId + '\''
+ ", mSliceUri=" + mSliceUri
+ ", mWidget=" + mWidget
+ + ", mTemplateData=" + mTemplateData
+ '}';
}
@@ -457,7 +499,8 @@
&& Objects.equals(mAssociatedSmartspaceTargetId,
that.mAssociatedSmartspaceTargetId)
&& Objects.equals(mSliceUri, that.mSliceUri)
- && Objects.equals(mWidget, that.mWidget);
+ && Objects.equals(mWidget, that.mWidget)
+ && Objects.equals(mTemplateData, that.mTemplateData);
}
@Override
@@ -465,7 +508,7 @@
return Objects.hash(mSmartspaceTargetId, mHeaderAction, mBaseAction, mCreationTimeMillis,
mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive,
mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle,
- mAssociatedSmartspaceTargetId, mSliceUri, mWidget);
+ mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData);
}
/**
@@ -476,6 +519,9 @@
@SystemApi
public static final class Builder {
private final String mSmartspaceTargetId;
+ private final ComponentName mComponentName;
+ private final UserHandle mUserHandle;
+
private SmartspaceAction mHeaderAction;
private SmartspaceAction mBaseAction;
private long mCreationTimeMillis;
@@ -487,11 +533,10 @@
private boolean mSensitive;
private boolean mShouldShowExpanded;
private String mSourceNotificationKey;
- private final ComponentName mComponentName;
- private final UserHandle mUserHandle;
private String mAssociatedSmartspaceTargetId;
private Uri mSliceUri;
private AppWidgetProviderInfo mWidget;
+ private SmartspaceDefaultUiTemplateData mTemplateData;
/**
* A builder for {@link SmartspaceTarget}.
@@ -640,6 +685,16 @@
}
/**
+ * Sets the UI template data.
+ */
+ @NonNull
+ public Builder setTemplateData(
+ @Nullable SmartspaceDefaultUiTemplateData templateData) {
+ mTemplateData = templateData;
+ return this;
+ }
+
+ /**
* Builds a new {@link SmartspaceTarget}.
*
* @throws IllegalStateException when non null fields are set as null.
@@ -655,7 +710,7 @@
mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore,
mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded,
mSourceNotificationKey, mComponentName, mUserHandle,
- mAssociatedSmartspaceTargetId, mSliceUri, mWidget);
+ mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData);
}
}
}
diff --git a/core/java/android/app/smartspace/SmartspaceUtils.java b/core/java/android/app/smartspace/SmartspaceUtils.java
new file mode 100644
index 0000000..f058ffa
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceUtils.java
@@ -0,0 +1,37 @@
+/*
+ * 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.app.smartspace;
+
+import android.annotation.Nullable;
+
+/**
+ * Utilities for Smartspace data.
+ *
+ * @hide
+ */
+public final class SmartspaceUtils {
+
+ private SmartspaceUtils() {
+ }
+
+ /** Returns true if the passed-in {@link CharSequence}s are equal. */
+ public static boolean isEqual(@Nullable CharSequence cs1, @Nullable CharSequence cs2) {
+ if ((cs1 == null && cs2 != null) || (cs1 != null && cs2 == null)) return false;
+ if (cs1 == null && cs2 == null) return true;
+ return cs1.toString().contentEquals(cs2);
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceCarouselUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCarouselUiTemplateData.java
new file mode 100644
index 0000000..c4c4fde
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCarouselUiTemplateData.java
@@ -0,0 +1,360 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the carousel Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceCarouselUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+ /** Lists of {@link CarouselItem}. */
+ @NonNull
+ private final List<CarouselItem> mCarouselItems;
+
+ /** Tap action for the entire carousel secondary card, including the blank space */
+ @Nullable
+ private final SmartspaceTapAction mCarouselAction;
+
+ SmartspaceCarouselUiTemplateData(@NonNull Parcel in) {
+ super(in);
+ mCarouselItems = in.createTypedArrayList(CarouselItem.CREATOR);
+ mCarouselAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ }
+
+ private SmartspaceCarouselUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText,
+ @NonNull List<CarouselItem> carouselItems,
+ @Nullable SmartspaceTapAction carouselAction) {
+ super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+ supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+ supplementalAlarmText);
+ mCarouselItems = carouselItems;
+ mCarouselAction = carouselAction;
+ }
+
+ @NonNull
+ public List<CarouselItem> getCarouselItems() {
+ return mCarouselItems;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getCarouselAction() {
+ return mCarouselAction;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceCarouselUiTemplateData> CREATOR =
+ new Creator<SmartspaceCarouselUiTemplateData>() {
+ @Override
+ public SmartspaceCarouselUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceCarouselUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceCarouselUiTemplateData[] newArray(int size) {
+ return new SmartspaceCarouselUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeTypedList(mCarouselItems);
+ out.writeTypedObject(mCarouselAction, flags);
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceCarouselUiTemplateData)) return false;
+ if (!super.equals(o)) return false;
+ SmartspaceCarouselUiTemplateData that = (SmartspaceCarouselUiTemplateData) o;
+ return mCarouselItems.equals(that.mCarouselItems) && Objects.equals(mCarouselAction,
+ that.mCarouselAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mCarouselItems, mCarouselAction);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " + SmartspaceCarouselUiTemplateData{"
+ + "mCarouselItems=" + mCarouselItems
+ + ", mCarouselActions=" + mCarouselAction
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceCarouselUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+ private final List<CarouselItem> mCarouselItems;
+ private SmartspaceTapAction mCarouselAction;
+
+ /**
+ * A builder for {@link SmartspaceCarouselUiTemplateData}.
+ */
+ public Builder(@NonNull List<CarouselItem> carouselItems) {
+ super(SmartspaceTarget.UI_TEMPLATE_CAROUSEL);
+ mCarouselItems = Objects.requireNonNull(carouselItems);
+ }
+
+ /**
+ * Sets the card tap action.
+ */
+ @NonNull
+ public Builder setCarouselAction(@NonNull SmartspaceTapAction carouselAction) {
+ mCarouselAction = carouselAction;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceCarouselUiTemplateData instance.
+ *
+ * @throws IllegalStateException if the carousel data is invalid.
+ */
+ @NonNull
+ public SmartspaceCarouselUiTemplateData build() {
+ if (mCarouselItems.isEmpty()) {
+ throw new IllegalStateException("Carousel data is empty");
+ }
+ return new SmartspaceCarouselUiTemplateData(getTemplateType(), getTitleText(),
+ getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+ getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+ getSupplementalSubtitleTapAction(), getSupplementalAlarmText(),
+ mCarouselItems,
+ mCarouselAction);
+ }
+ }
+
+ /** Holds all the relevant data needed to render a carousel item. */
+ public static final class CarouselItem implements Parcelable {
+
+ /** Text which is above the image item. */
+ @Nullable
+ private final CharSequence mUpperText;
+
+ /** Image item. Can be empty. */
+ @Nullable
+ private final SmartspaceIcon mImage;
+
+ /** Text which is under the image item. */
+ @Nullable
+ private final CharSequence mLowerText;
+
+ /**
+ * Tap action for this {@link CarouselItem} instance. {@code mCarouselAction} is used if not
+ * being set.
+ */
+ @Nullable
+ private final SmartspaceTapAction mTapAction;
+
+ CarouselItem(@NonNull Parcel in) {
+ mUpperText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mImage = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mLowerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mTapAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ }
+
+ private CarouselItem(@Nullable CharSequence upperText, @Nullable SmartspaceIcon image,
+ @Nullable CharSequence lowerText, @Nullable SmartspaceTapAction tapAction) {
+ mUpperText = upperText;
+ mImage = image;
+ mLowerText = lowerText;
+ mTapAction = tapAction;
+ }
+
+ @Nullable
+ public CharSequence getUpperText() {
+ return mUpperText;
+ }
+
+ @Nullable
+ public SmartspaceIcon getImage() {
+ return mImage;
+ }
+
+ @Nullable
+ public CharSequence getLowerText() {
+ return mLowerText;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getTapAction() {
+ return mTapAction;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<CarouselItem> CREATOR =
+ new Creator<CarouselItem>() {
+ @Override
+ public CarouselItem createFromParcel(Parcel in) {
+ return new CarouselItem(in);
+ }
+
+ @Override
+ public CarouselItem[] newArray(int size) {
+ return new CarouselItem[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ TextUtils.writeToParcel(mUpperText, out, flags);
+ out.writeTypedObject(mImage, flags);
+ TextUtils.writeToParcel(mLowerText, out, flags);
+ out.writeTypedObject(mTapAction, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof CarouselItem)) return false;
+ CarouselItem that = (CarouselItem) o;
+ return SmartspaceUtils.isEqual(mUpperText, that.mUpperText) && Objects.equals(
+ mImage,
+ that.mImage) && SmartspaceUtils.isEqual(mLowerText, that.mLowerText)
+ && Objects.equals(mTapAction, that.mTapAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUpperText, mImage, mLowerText, mTapAction);
+ }
+
+ @Override
+ public String toString() {
+ return "CarouselItem{"
+ + "mUpperText=" + mUpperText
+ + ", mImage=" + mImage
+ + ", mLowerText=" + mLowerText
+ + ", mTapAction=" + mTapAction
+ + '}';
+ }
+
+ /**
+ * A builder for {@link CarouselItem} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ private CharSequence mUpperText;
+ private SmartspaceIcon mImage;
+ private CharSequence mLowerText;
+ private SmartspaceTapAction mTapAction;
+
+ /**
+ * Sets the upper text.
+ */
+ @NonNull
+ public Builder setUpperText(@Nullable CharSequence upperText) {
+ mUpperText = upperText;
+ return this;
+ }
+
+ /**
+ * Sets the image.
+ */
+ @NonNull
+ public Builder setImage(@Nullable SmartspaceIcon image) {
+ mImage = image;
+ return this;
+ }
+
+
+ /**
+ * Sets the lower text.
+ */
+ @NonNull
+ public Builder setLowerText(@Nullable CharSequence lowerText) {
+ mLowerText = lowerText;
+ return this;
+ }
+
+ /**
+ * Sets the tap action.
+ */
+ @NonNull
+ public Builder setTapAction(@Nullable SmartspaceTapAction tapAction) {
+ mTapAction = tapAction;
+ return this;
+ }
+
+ /**
+ * Builds a new CarouselItem instance.
+ *
+ * @throws IllegalStateException if all the rendering data is empty.
+ */
+ @NonNull
+ public CarouselItem build() {
+ if (TextUtils.isEmpty(mUpperText) && mImage == null && TextUtils.isEmpty(
+ mLowerText)) {
+ throw new IllegalStateException("Carousel data is empty");
+ }
+ return new CarouselItem(mUpperText, mImage, mLowerText, mTapAction);
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceCombinedCardsUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCombinedCardsUiTemplateData.java
new file mode 100644
index 0000000..7e2f74e
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCombinedCardsUiTemplateData.java
@@ -0,0 +1,155 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.os.Parcel;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the combined-card Ui
+ * Template.
+ *
+ * We only support 1 sub-list card combined with 1 carousel card. And we may expand our supported
+ * combinations in the future.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceCombinedCardsUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+ /** A list of secondary cards. */
+ @NonNull
+ private final List<SmartspaceDefaultUiTemplateData> mCombinedCardDataList;
+
+ SmartspaceCombinedCardsUiTemplateData(@NonNull Parcel in) {
+ super(in);
+ mCombinedCardDataList = in.createTypedArrayList(SmartspaceDefaultUiTemplateData.CREATOR);
+ }
+
+ private SmartspaceCombinedCardsUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText,
+ @NonNull List<SmartspaceDefaultUiTemplateData> combinedCardDataList) {
+ super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+ supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+ supplementalAlarmText);
+ mCombinedCardDataList = combinedCardDataList;
+ }
+
+ @NonNull
+ public List<SmartspaceDefaultUiTemplateData> getCombinedCardDataList() {
+ return mCombinedCardDataList;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceCombinedCardsUiTemplateData> CREATOR =
+ new Creator<SmartspaceCombinedCardsUiTemplateData>() {
+ @Override
+ public SmartspaceCombinedCardsUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceCombinedCardsUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceCombinedCardsUiTemplateData[] newArray(int size) {
+ return new SmartspaceCombinedCardsUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeTypedList(mCombinedCardDataList);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceCombinedCardsUiTemplateData)) return false;
+ if (!super.equals(o)) return false;
+ SmartspaceCombinedCardsUiTemplateData that = (SmartspaceCombinedCardsUiTemplateData) o;
+ return mCombinedCardDataList.equals(that.mCombinedCardDataList);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mCombinedCardDataList);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " + SmartspaceCombinedCardsUiTemplateData{"
+ + "mCombinedCardDataList=" + mCombinedCardDataList
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceCombinedCardsUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+ private final List<SmartspaceDefaultUiTemplateData> mCombinedCardDataList;
+
+ /**
+ * A builder for {@link SmartspaceCombinedCardsUiTemplateData}.
+ */
+ public Builder(@NonNull List<SmartspaceDefaultUiTemplateData> combinedCardDataList) {
+ super(SmartspaceTarget.UI_TEMPLATE_COMBINED_CARDS);
+ mCombinedCardDataList = Objects.requireNonNull(combinedCardDataList);
+ }
+
+ /**
+ * Builds a new SmartspaceCombinedCardsUiTemplateData instance.
+ *
+ * @throws IllegalStateException if any required non-null field is null
+ */
+ @NonNull
+ public SmartspaceCombinedCardsUiTemplateData build() {
+ if (mCombinedCardDataList == null) {
+ throw new IllegalStateException("Please assign a value to all @NonNull args.");
+ }
+ return new SmartspaceCombinedCardsUiTemplateData(getTemplateType(), getTitleText(),
+ getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+ getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+ getSupplementalSubtitleTapAction(), getSupplementalAlarmText(),
+ mCombinedCardDataList);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceDefaultUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceDefaultUiTemplateData.java
new file mode 100644
index 0000000..742d5c9
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceDefaultUiTemplateData.java
@@ -0,0 +1,459 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget.UiTemplateType;
+import android.app.smartspace.SmartspaceUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the default Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+@SuppressLint("ParcelNotFinal")
+public class SmartspaceDefaultUiTemplateData implements Parcelable {
+
+ /**
+ * {@link UiTemplateType} indicating the template type of this template data.
+ *
+ * @see UiTemplateType
+ */
+ @UiTemplateType
+ private final int mTemplateType;
+
+ /**
+ * Title text and title icon are shown at the first row. When both are absent, the date view
+ * will be used, which has its own tap action applied to the title area.
+ */
+ @Nullable
+ private final CharSequence mTitleText;
+
+ @Nullable
+ private final SmartspaceIcon mTitleIcon;
+
+ /** Subtitle text and icon are shown at the second row. */
+ @Nullable
+ private final CharSequence mSubtitleText;
+
+ @Nullable
+ private final SmartspaceIcon mSubTitleIcon;
+
+ /**
+ * Primary tap action for the entire card, including the blank spaces, except: 1. When title is
+ * absent, the date view's default tap action is used; 2. Supplemental subtitle uses its own tap
+ * action if being set; 3. Secondary card uses its own tap action if being set.
+ */
+ @Nullable
+ private final SmartspaceTapAction mPrimaryTapAction;
+
+ /**
+ * Supplemental subtitle text and icon are shown at the second row following the subtitle text.
+ * Mainly used for weather info on non-weather card.
+ */
+ @Nullable
+ private final CharSequence mSupplementalSubtitleText;
+
+ @Nullable
+ private final SmartspaceIcon mSupplementalSubtitleIcon;
+
+ /**
+ * Tap action for the supplemental subtitle's text and icon. Will use the primary tap action if
+ * not being set.
+ */
+ @Nullable
+ private final SmartspaceTapAction mSupplementalSubtitleTapAction;
+
+ /**
+ * Supplemental alarm text is specifically used for holiday alarm, which is appended to "next
+ * alarm".
+ */
+ @Nullable
+ private final CharSequence mSupplementalAlarmText;
+
+ SmartspaceDefaultUiTemplateData(@NonNull Parcel in) {
+ mTemplateType = in.readInt();
+ mTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mTitleIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mSubtitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSubTitleIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mPrimaryTapAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ mSupplementalSubtitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSupplementalSubtitleIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mSupplementalSubtitleTapAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ mSupplementalAlarmText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ }
+
+ /**
+ * Should ONLY used by subclasses. For the general instance creation, please use
+ * SmartspaceDefaultUiTemplateData.Builder.
+ */
+ SmartspaceDefaultUiTemplateData(@UiTemplateType int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText) {
+ mTemplateType = templateType;
+ mTitleText = titleText;
+ mTitleIcon = titleIcon;
+ mSubtitleText = subtitleText;
+ mSubTitleIcon = subTitleIcon;
+ mPrimaryTapAction = primaryTapAction;
+ mSupplementalSubtitleText = supplementalSubtitleText;
+ mSupplementalSubtitleIcon = supplementalSubtitleIcon;
+ mSupplementalSubtitleTapAction = supplementalSubtitleTapAction;
+ mSupplementalAlarmText = supplementalAlarmText;
+ }
+
+ @UiTemplateType
+ public int getTemplateType() {
+ return mTemplateType;
+ }
+
+ @Nullable
+ public CharSequence getTitleText() {
+ return mTitleText;
+ }
+
+ @Nullable
+ public SmartspaceIcon getTitleIcon() {
+ return mTitleIcon;
+ }
+
+ @Nullable
+ public CharSequence getSubtitleText() {
+ return mSubtitleText;
+ }
+
+ @Nullable
+ public SmartspaceIcon getSubTitleIcon() {
+ return mSubTitleIcon;
+ }
+
+ @Nullable
+ public CharSequence getSupplementalSubtitleText() {
+ return mSupplementalSubtitleText;
+ }
+
+ @Nullable
+ public SmartspaceIcon getSupplementalSubtitleIcon() {
+ return mSupplementalSubtitleIcon;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getPrimaryTapAction() {
+ return mPrimaryTapAction;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getSupplementalSubtitleTapAction() {
+ return mSupplementalSubtitleTapAction;
+ }
+
+ @Nullable
+ public CharSequence getSupplementalAlarmText() {
+ return mSupplementalAlarmText;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceDefaultUiTemplateData> CREATOR =
+ new Creator<SmartspaceDefaultUiTemplateData>() {
+ @Override
+ public SmartspaceDefaultUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceDefaultUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceDefaultUiTemplateData[] newArray(int size) {
+ return new SmartspaceDefaultUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mTemplateType);
+ TextUtils.writeToParcel(mTitleText, out, flags);
+ out.writeTypedObject(mTitleIcon, flags);
+ TextUtils.writeToParcel(mSubtitleText, out, flags);
+ out.writeTypedObject(mSubTitleIcon, flags);
+ out.writeTypedObject(mPrimaryTapAction, flags);
+ TextUtils.writeToParcel(mSupplementalSubtitleText, out, flags);
+ out.writeTypedObject(mSupplementalSubtitleIcon, flags);
+ out.writeTypedObject(mSupplementalSubtitleTapAction, flags);
+ TextUtils.writeToParcel(mSupplementalAlarmText, out, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceDefaultUiTemplateData)) return false;
+ SmartspaceDefaultUiTemplateData that = (SmartspaceDefaultUiTemplateData) o;
+ return mTemplateType == that.mTemplateType && SmartspaceUtils.isEqual(mTitleText,
+ that.mTitleText)
+ && Objects.equals(mTitleIcon, that.mTitleIcon)
+ && SmartspaceUtils.isEqual(mSubtitleText, that.mSubtitleText)
+ && Objects.equals(mSubTitleIcon, that.mSubTitleIcon)
+ && Objects.equals(mPrimaryTapAction, that.mPrimaryTapAction)
+ && SmartspaceUtils.isEqual(mSupplementalSubtitleText,
+ that.mSupplementalSubtitleText)
+ && Objects.equals(mSupplementalSubtitleIcon, that.mSupplementalSubtitleIcon)
+ && Objects.equals(mSupplementalSubtitleTapAction,
+ that.mSupplementalSubtitleTapAction)
+ && SmartspaceUtils.isEqual(mSupplementalAlarmText, that.mSupplementalAlarmText);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTemplateType, mTitleText, mTitleIcon, mSubtitleText, mSubTitleIcon,
+ mPrimaryTapAction, mSupplementalSubtitleText, mSupplementalSubtitleIcon,
+ mSupplementalSubtitleTapAction, mSupplementalAlarmText);
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceDefaultUiTemplateData{"
+ + "mTemplateType=" + mTemplateType
+ + ", mTitleText=" + mTitleText
+ + ", mTitleIcon=" + mTitleIcon
+ + ", mSubtitleText=" + mSubtitleText
+ + ", mSubTitleIcon=" + mSubTitleIcon
+ + ", mPrimaryTapAction=" + mPrimaryTapAction
+ + ", mSupplementalSubtitleText=" + mSupplementalSubtitleText
+ + ", mSupplementalSubtitleIcon=" + mSupplementalSubtitleIcon
+ + ", mSupplementalSubtitleTapAction=" + mSupplementalSubtitleTapAction
+ + ", mSupplementalAlarmText=" + mSupplementalAlarmText
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceDefaultUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("StaticFinalBuilder")
+ public static class Builder {
+ @UiTemplateType
+ private final int mTemplateType;
+ private CharSequence mTitleText;
+ private SmartspaceIcon mTitleIcon;
+ private CharSequence mSubtitleText;
+ private SmartspaceIcon mSubTitleIcon;
+ private SmartspaceTapAction mPrimaryTapAction;
+ private CharSequence mSupplementalSubtitleText;
+ private SmartspaceIcon mSupplementalSubtitleIcon;
+ private SmartspaceTapAction mSupplementalSubtitleTapAction;
+ private CharSequence mSupplementalAlarmText;
+
+ /**
+ * A builder for {@link SmartspaceDefaultUiTemplateData}.
+ *
+ * @param templateType the {@link UiTemplateType} of this template data.
+ */
+ public Builder(@UiTemplateType int templateType) {
+ mTemplateType = templateType;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @UiTemplateType
+ @SuppressLint("GetterOnBuilder")
+ int getTemplateType() {
+ return mTemplateType;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ CharSequence getTitleText() {
+ return mTitleText;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ SmartspaceIcon getTitleIcon() {
+ return mTitleIcon;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ CharSequence getSubtitleText() {
+ return mSubtitleText;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ SmartspaceIcon getSubTitleIcon() {
+ return mSubTitleIcon;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ SmartspaceTapAction getPrimaryTapAction() {
+ return mPrimaryTapAction;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ CharSequence getSupplementalSubtitleText() {
+ return mSupplementalSubtitleText;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ SmartspaceIcon getSupplementalSubtitleIcon() {
+ return mSupplementalSubtitleIcon;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ SmartspaceTapAction getSupplementalSubtitleTapAction() {
+ return mSupplementalSubtitleTapAction;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ CharSequence getSupplementalAlarmText() {
+ return mSupplementalAlarmText;
+ }
+
+ /**
+ * Sets the card title.
+ */
+ @NonNull
+ public Builder setTitleText(@NonNull CharSequence titleText) {
+ mTitleText = titleText;
+ return this;
+ }
+
+ /**
+ * Sets the card title icon.
+ */
+ @NonNull
+ public Builder setTitleIcon(@NonNull SmartspaceIcon titleIcon) {
+ mTitleIcon = titleIcon;
+ return this;
+ }
+
+ /**
+ * Sets the card subtitle.
+ */
+ @NonNull
+ public Builder setSubtitleText(@NonNull CharSequence subtitleText) {
+ mSubtitleText = subtitleText;
+ return this;
+ }
+
+ /**
+ * Sets the card subtitle icon.
+ */
+ @NonNull
+ public Builder setSubTitleIcon(@NonNull SmartspaceIcon subTitleIcon) {
+ mSubTitleIcon = subTitleIcon;
+ return this;
+ }
+
+ /**
+ * Sets the card primary tap action.
+ */
+ @NonNull
+ public Builder setPrimaryTapAction(@NonNull SmartspaceTapAction primaryTapAction) {
+ mPrimaryTapAction = primaryTapAction;
+ return this;
+ }
+
+ /**
+ * Sets the supplemental subtitle text.
+ */
+ @NonNull
+ public Builder setSupplementalSubtitleText(@NonNull CharSequence supplementalSubtitleText) {
+ mSupplementalSubtitleText = supplementalSubtitleText;
+ return this;
+ }
+
+ /**
+ * Sets the supplemental subtitle icon.
+ */
+ @NonNull
+ public Builder setSupplementalSubtitleIcon(
+ @NonNull SmartspaceIcon supplementalSubtitleIcon) {
+ mSupplementalSubtitleIcon = supplementalSubtitleIcon;
+ return this;
+ }
+
+ /**
+ * Sets the supplemental subtitle tap action. {@code mPrimaryTapAction} will be used if not
+ * being
+ * set.
+ */
+ @NonNull
+ public Builder setSupplementalSubtitleTapAction(
+ @NonNull SmartspaceTapAction supplementalSubtitleTapAction) {
+ mSupplementalSubtitleTapAction = supplementalSubtitleTapAction;
+ return this;
+ }
+
+ /**
+ * Sets the supplemental alarm text.
+ */
+ @NonNull
+ public Builder setSupplementalAlarmText(@NonNull CharSequence supplementalAlarmText) {
+ mSupplementalAlarmText = supplementalAlarmText;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceDefaultUiTemplateData instance.
+ */
+ @NonNull
+ public SmartspaceDefaultUiTemplateData build() {
+ return new SmartspaceDefaultUiTemplateData(mTemplateType, mTitleText, mTitleIcon,
+ mSubtitleText, mSubTitleIcon, mPrimaryTapAction, mSupplementalSubtitleText,
+ mSupplementalSubtitleIcon, mSupplementalSubtitleTapAction,
+ mSupplementalAlarmText);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceHeadToHeadUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceHeadToHeadUiTemplateData.java
new file mode 100644
index 0000000..c76af27
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceHeadToHeadUiTemplateData.java
@@ -0,0 +1,286 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceUtils;
+import android.os.Parcel;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the head-to-head Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceHeadToHeadUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+ @Nullable
+ private final CharSequence mHeadToHeadTitle;
+ @Nullable
+ private final SmartspaceIcon mHeadToHeadFirstCompetitorIcon;
+ @Nullable
+ private final SmartspaceIcon mHeadToHeadSecondCompetitorIcon;
+ @Nullable
+ private final CharSequence mHeadToHeadFirstCompetitorText;
+ @Nullable
+ private final CharSequence mHeadToHeadSecondCompetitorText;
+
+ /** Tap action for the head-to-head secondary card. */
+ @Nullable
+ private final SmartspaceTapAction mHeadToHeadAction;
+
+ SmartspaceHeadToHeadUiTemplateData(@NonNull Parcel in) {
+ super(in);
+ mHeadToHeadTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mHeadToHeadFirstCompetitorIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mHeadToHeadSecondCompetitorIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mHeadToHeadFirstCompetitorText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mHeadToHeadSecondCompetitorText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mHeadToHeadAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ }
+
+ private SmartspaceHeadToHeadUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText,
+ @Nullable CharSequence headToHeadTitle,
+ @Nullable SmartspaceIcon headToHeadFirstCompetitorIcon,
+ @Nullable SmartspaceIcon headToHeadSecondCompetitorIcon,
+ @Nullable CharSequence headToHeadFirstCompetitorText,
+ @Nullable CharSequence headToHeadSecondCompetitorText,
+ @Nullable SmartspaceTapAction headToHeadAction) {
+ super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+ supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+ supplementalAlarmText);
+ mHeadToHeadTitle = headToHeadTitle;
+ mHeadToHeadFirstCompetitorIcon = headToHeadFirstCompetitorIcon;
+ mHeadToHeadSecondCompetitorIcon = headToHeadSecondCompetitorIcon;
+ mHeadToHeadFirstCompetitorText = headToHeadFirstCompetitorText;
+ mHeadToHeadSecondCompetitorText = headToHeadSecondCompetitorText;
+ mHeadToHeadAction = headToHeadAction;
+ }
+
+ @Nullable
+ public CharSequence getHeadToHeadTitle() {
+ return mHeadToHeadTitle;
+ }
+
+ @Nullable
+ public SmartspaceIcon getHeadToHeadFirstCompetitorIcon() {
+ return mHeadToHeadFirstCompetitorIcon;
+ }
+
+ @Nullable
+ public SmartspaceIcon getHeadToHeadSecondCompetitorIcon() {
+ return mHeadToHeadSecondCompetitorIcon;
+ }
+
+ @Nullable
+ public CharSequence getHeadToHeadFirstCompetitorText() {
+ return mHeadToHeadFirstCompetitorText;
+ }
+
+ @Nullable
+ public CharSequence getHeadToHeadSecondCompetitorText() {
+ return mHeadToHeadSecondCompetitorText;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getHeadToHeadAction() {
+ return mHeadToHeadAction;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceHeadToHeadUiTemplateData> CREATOR =
+ new Creator<SmartspaceHeadToHeadUiTemplateData>() {
+ @Override
+ public SmartspaceHeadToHeadUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceHeadToHeadUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceHeadToHeadUiTemplateData[] newArray(int size) {
+ return new SmartspaceHeadToHeadUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ TextUtils.writeToParcel(mHeadToHeadTitle, out, flags);
+ out.writeTypedObject(mHeadToHeadFirstCompetitorIcon, flags);
+ out.writeTypedObject(mHeadToHeadSecondCompetitorIcon, flags);
+ TextUtils.writeToParcel(mHeadToHeadFirstCompetitorText, out, flags);
+ TextUtils.writeToParcel(mHeadToHeadSecondCompetitorText, out, flags);
+ out.writeTypedObject(mHeadToHeadAction, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceHeadToHeadUiTemplateData)) return false;
+ if (!super.equals(o)) return false;
+ SmartspaceHeadToHeadUiTemplateData that = (SmartspaceHeadToHeadUiTemplateData) o;
+ return SmartspaceUtils.isEqual(mHeadToHeadTitle, that.mHeadToHeadTitle) && Objects.equals(
+ mHeadToHeadFirstCompetitorIcon, that.mHeadToHeadFirstCompetitorIcon)
+ && Objects.equals(
+ mHeadToHeadSecondCompetitorIcon, that.mHeadToHeadSecondCompetitorIcon)
+ && SmartspaceUtils.isEqual(mHeadToHeadFirstCompetitorText,
+ that.mHeadToHeadFirstCompetitorText)
+ && SmartspaceUtils.isEqual(mHeadToHeadSecondCompetitorText,
+ that.mHeadToHeadSecondCompetitorText)
+ && Objects.equals(
+ mHeadToHeadAction, that.mHeadToHeadAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mHeadToHeadTitle, mHeadToHeadFirstCompetitorIcon,
+ mHeadToHeadSecondCompetitorIcon, mHeadToHeadFirstCompetitorText,
+ mHeadToHeadSecondCompetitorText,
+ mHeadToHeadAction);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " + SmartspaceHeadToHeadUiTemplateData{"
+ + "mH2HTitle=" + mHeadToHeadTitle
+ + ", mH2HFirstCompetitorIcon=" + mHeadToHeadFirstCompetitorIcon
+ + ", mH2HSecondCompetitorIcon=" + mHeadToHeadSecondCompetitorIcon
+ + ", mH2HFirstCompetitorText=" + mHeadToHeadFirstCompetitorText
+ + ", mH2HSecondCompetitorText=" + mHeadToHeadSecondCompetitorText
+ + ", mH2HAction=" + mHeadToHeadAction
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceHeadToHeadUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+ private CharSequence mHeadToHeadTitle;
+ private SmartspaceIcon mHeadToHeadFirstCompetitorIcon;
+ private SmartspaceIcon mHeadToHeadSecondCompetitorIcon;
+ private CharSequence mHeadToHeadFirstCompetitorText;
+ private CharSequence mHeadToHeadSecondCompetitorText;
+ private SmartspaceTapAction mHeadToHeadAction;
+
+ /**
+ * A builder for {@link SmartspaceHeadToHeadUiTemplateData}.
+ */
+ public Builder() {
+ super(SmartspaceTarget.UI_TEMPLATE_HEAD_TO_HEAD);
+ }
+
+ /**
+ * Sets the head-to-head card's title
+ */
+ @NonNull
+ public Builder setHeadToHeadTitle(@Nullable CharSequence headToHeadTitle) {
+ mHeadToHeadTitle = headToHeadTitle;
+ return this;
+ }
+
+ /**
+ * Sets the head-to-head card's first competitor icon
+ */
+ @NonNull
+ public Builder setHeadToHeadFirstCompetitorIcon(
+ @Nullable SmartspaceIcon headToHeadFirstCompetitorIcon) {
+ mHeadToHeadFirstCompetitorIcon = headToHeadFirstCompetitorIcon;
+ return this;
+ }
+
+ /**
+ * Sets the head-to-head card's second competitor icon
+ */
+ @NonNull
+ public Builder setHeadToHeadSecondCompetitorIcon(
+ @Nullable SmartspaceIcon headToHeadSecondCompetitorIcon) {
+ mHeadToHeadSecondCompetitorIcon = headToHeadSecondCompetitorIcon;
+ return this;
+ }
+
+ /**
+ * Sets the head-to-head card's first competitor text
+ */
+ @NonNull
+ public Builder setHeadToHeadFirstCompetitorText(
+ @Nullable CharSequence headToHeadFirstCompetitorText) {
+ mHeadToHeadFirstCompetitorText = headToHeadFirstCompetitorText;
+ return this;
+ }
+
+ /**
+ * Sets the head-to-head card's second competitor text
+ */
+ @NonNull
+ public Builder setHeadToHeadSecondCompetitorText(
+ @Nullable CharSequence headToHeadSecondCompetitorText) {
+ mHeadToHeadSecondCompetitorText = headToHeadSecondCompetitorText;
+ return this;
+ }
+
+ /**
+ * Sets the head-to-head card's tap action
+ */
+ @NonNull
+ public Builder setHeadToHeadAction(@Nullable SmartspaceTapAction headToHeadAction) {
+ mHeadToHeadAction = headToHeadAction;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceHeadToHeadUiTemplateData instance.
+ */
+ @NonNull
+ public SmartspaceHeadToHeadUiTemplateData build() {
+ return new SmartspaceHeadToHeadUiTemplateData(getTemplateType(), getTitleText(),
+ getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+ getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+ getSupplementalSubtitleTapAction(), getSupplementalAlarmText(),
+ mHeadToHeadTitle,
+ mHeadToHeadFirstCompetitorIcon,
+ mHeadToHeadSecondCompetitorIcon, mHeadToHeadFirstCompetitorText,
+ mHeadToHeadSecondCompetitorText,
+ mHeadToHeadAction);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceIcon.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceIcon.java
new file mode 100644
index 0000000..70b3095
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceIcon.java
@@ -0,0 +1,148 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceUtils;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Holds the information for a Smartspace-card icon. Including the icon image itself, and an
+ * optional content description as the icon's accessibility description.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceIcon implements Parcelable {
+
+ @NonNull
+ private final Icon mIcon;
+
+ @Nullable
+ private final CharSequence mContentDescription;
+
+ SmartspaceIcon(@NonNull Parcel in) {
+ mIcon = in.readTypedObject(Icon.CREATOR);
+ mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ }
+
+ private SmartspaceIcon(@NonNull Icon icon, @Nullable CharSequence contentDescription) {
+ mIcon = icon;
+ mContentDescription = contentDescription;
+ }
+
+ @NonNull
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ @Nullable
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ @NonNull
+ public static final Creator<SmartspaceIcon> CREATOR = new Creator<SmartspaceIcon>() {
+ @Override
+ public SmartspaceIcon createFromParcel(Parcel in) {
+ return new SmartspaceIcon(in);
+ }
+
+ @Override
+ public SmartspaceIcon[] newArray(int size) {
+ return new SmartspaceIcon[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceIcon)) return false;
+ SmartspaceIcon that = (SmartspaceIcon) o;
+ return mIcon.equals(that.mIcon) && SmartspaceUtils.isEqual(mContentDescription,
+ that.mContentDescription);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIcon, mContentDescription);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeTypedObject(mIcon, flags);
+ TextUtils.writeToParcel(mContentDescription, out, flags);
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceIcon{"
+ + "mImage=" + mIcon
+ + ", mContentDescription='" + mContentDescription + '\''
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceIcon} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ private Icon mIcon;
+ private CharSequence mContentDescription;
+
+ /**
+ * A builder for {@link SmartspaceIcon}.
+ *
+ * @param icon the icon image of this smartspace icon.
+ */
+ public Builder(@NonNull Icon icon) {
+ mIcon = Objects.requireNonNull(icon);
+ }
+
+ /**
+ * Sets the icon's content description.
+ */
+ @NonNull
+ public Builder setContentDescription(@NonNull CharSequence contentDescription) {
+ mContentDescription = contentDescription;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceIcon instance.
+ */
+ @NonNull
+ public SmartspaceIcon build() {
+ return new SmartspaceIcon(mIcon, mContentDescription);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubCardUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubCardUiTemplateData.java
new file mode 100644
index 0000000..287cf8e
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubCardUiTemplateData.java
@@ -0,0 +1,198 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceUtils;
+import android.os.Parcel;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the sub-card Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSubCardUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+ /** Icon for the sub-card. */
+ @NonNull
+ private final SmartspaceIcon mSubCardIcon;
+
+ /** Text for the sub-card, which shows below the icon when being set. */
+ @Nullable
+ private final CharSequence mSubCardText;
+
+ /** Tap action for the sub-card secondary card. */
+ @Nullable
+ private final SmartspaceTapAction mSubCardAction;
+
+ SmartspaceSubCardUiTemplateData(@NonNull Parcel in) {
+ super(in);
+ mSubCardIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mSubCardText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSubCardAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ }
+
+ private SmartspaceSubCardUiTemplateData(int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText,
+ @NonNull SmartspaceIcon subCardIcon,
+ @Nullable CharSequence subCardText,
+ @Nullable SmartspaceTapAction subCardAction) {
+ super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+ supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+ supplementalAlarmText);
+ mSubCardIcon = subCardIcon;
+ mSubCardText = subCardText;
+ mSubCardAction = subCardAction;
+ }
+
+ @NonNull
+ public SmartspaceIcon getSubCardIcon() {
+ return mSubCardIcon;
+ }
+
+ @Nullable
+ public CharSequence getSubCardText() {
+ return mSubCardText;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getSubCardAction() {
+ return mSubCardAction;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceSubCardUiTemplateData> CREATOR =
+ new Creator<SmartspaceSubCardUiTemplateData>() {
+ @Override
+ public SmartspaceSubCardUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceSubCardUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceSubCardUiTemplateData[] newArray(int size) {
+ return new SmartspaceSubCardUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeTypedObject(mSubCardIcon, flags);
+ TextUtils.writeToParcel(mSubCardText, out, flags);
+ out.writeTypedObject(mSubCardAction, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceSubCardUiTemplateData)) return false;
+ if (!super.equals(o)) return false;
+ SmartspaceSubCardUiTemplateData that = (SmartspaceSubCardUiTemplateData) o;
+ return mSubCardIcon.equals(that.mSubCardIcon) && SmartspaceUtils.isEqual(mSubCardText,
+ that.mSubCardText) && Objects.equals(mSubCardAction,
+ that.mSubCardAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mSubCardIcon, mSubCardText, mSubCardAction);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " + SmartspaceSubCardUiTemplateData{"
+ + "mSubCardIcon=" + mSubCardIcon
+ + ", mSubCardText=" + mSubCardText
+ + ", mSubCardAction=" + mSubCardAction
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceSubCardUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+ private final SmartspaceIcon mSubCardIcon;
+ private CharSequence mSubCardText;
+ private SmartspaceTapAction mSubCardAction;
+
+ /**
+ * A builder for {@link SmartspaceSubCardUiTemplateData}.
+ */
+ public Builder(@NonNull SmartspaceIcon subCardIcon) {
+ super(SmartspaceTarget.UI_TEMPLATE_SUB_CARD);
+ mSubCardIcon = Objects.requireNonNull(subCardIcon);
+ }
+
+ /**
+ * Sets the card title text.
+ */
+ @NonNull
+ public Builder setSubCardAction(@NonNull CharSequence subCardTitleText) {
+ mSubCardText = subCardTitleText;
+ return this;
+ }
+
+ /**
+ * Sets the card tap action.
+ */
+ @NonNull
+ public Builder setSubCardAction(@NonNull SmartspaceTapAction subCardAction) {
+ mSubCardAction = subCardAction;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceSubCardUiTemplateData instance.
+ */
+ @NonNull
+ public SmartspaceSubCardUiTemplateData build() {
+ return new SmartspaceSubCardUiTemplateData(getTemplateType(), getTitleText(),
+ getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+ getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+ getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubCardIcon,
+ mSubCardText,
+ mSubCardAction);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubImageUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubImageUiTemplateData.java
new file mode 100644
index 0000000..c479993
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubImageUiTemplateData.java
@@ -0,0 +1,192 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.os.Parcel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the sub-image Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSubImageUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+ /** Texts are shown next to the image as a vertical list */
+ @NonNull
+ private final List<CharSequence> mSubImageTexts;
+
+ /** If multiple images are passed in, they will be rendered as GIF. */
+ @NonNull
+ private final List<SmartspaceIcon> mSubImages;
+
+ /** Tap action for the sub-image secondary card. */
+ @Nullable
+ private final SmartspaceTapAction mSubImageAction;
+
+ SmartspaceSubImageUiTemplateData(@NonNull Parcel in) {
+ super(in);
+ mSubImageTexts = Arrays.asList(in.readCharSequenceArray());
+ mSubImages = in.createTypedArrayList(SmartspaceIcon.CREATOR);
+ mSubImageAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ }
+
+ private SmartspaceSubImageUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText,
+ @NonNull List<CharSequence> subImageTexts,
+ @NonNull List<SmartspaceIcon> subImages,
+ @Nullable SmartspaceTapAction subImageAction) {
+ super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+ supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+ supplementalAlarmText);
+ mSubImageTexts = subImageTexts;
+ mSubImages = subImages;
+ mSubImageAction = subImageAction;
+ }
+
+ @NonNull
+ public List<CharSequence> getSubImageTexts() {
+ return mSubImageTexts;
+ }
+
+ @NonNull
+ public List<SmartspaceIcon> getSubImages() {
+ return mSubImages;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getSubImageAction() {
+ return mSubImageAction;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceSubImageUiTemplateData> CREATOR =
+ new Creator<SmartspaceSubImageUiTemplateData>() {
+ @Override
+ public SmartspaceSubImageUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceSubImageUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceSubImageUiTemplateData[] newArray(int size) {
+ return new SmartspaceSubImageUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeCharSequenceList(new ArrayList<>(mSubImageTexts));
+ out.writeTypedList(mSubImages);
+ out.writeTypedObject(mSubImageAction, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceSubImageUiTemplateData)) return false;
+ if (!super.equals(o)) return false;
+ SmartspaceSubImageUiTemplateData that = (SmartspaceSubImageUiTemplateData) o;
+ return Objects.equals(mSubImageTexts, that.mSubImageTexts)
+ && Objects.equals(mSubImages, that.mSubImages) && Objects.equals(
+ mSubImageAction, that.mSubImageAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mSubImageTexts, mSubImages, mSubImageAction);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " + SmartspaceSubImageUiTemplateData{"
+ + "mSubImageTexts=" + mSubImageTexts
+ + ", mSubImages=" + mSubImages
+ + ", mSubImageAction=" + mSubImageAction
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceSubImageUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+ private final List<CharSequence> mSubImageTexts;
+ private final List<SmartspaceIcon> mSubImages;
+ private SmartspaceTapAction mSubImageAction;
+
+ /**
+ * A builder for {@link SmartspaceSubImageUiTemplateData}.
+ */
+ public Builder(@NonNull List<CharSequence> subImageTexts,
+ @NonNull List<SmartspaceIcon> subImages) {
+ super(SmartspaceTarget.UI_TEMPLATE_SUB_IMAGE);
+ mSubImageTexts = Objects.requireNonNull(subImageTexts);
+ mSubImages = Objects.requireNonNull(subImages);
+ }
+
+ /**
+ * Sets the card tap action.
+ */
+ @NonNull
+ public Builder setCarouselAction(@NonNull SmartspaceTapAction subImageAction) {
+ mSubImageAction = subImageAction;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceSubImageUiTemplateData instance.
+ */
+ @NonNull
+ public SmartspaceSubImageUiTemplateData build() {
+ return new SmartspaceSubImageUiTemplateData(getTemplateType(), getTitleText(),
+ getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+ getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+ getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubImageTexts,
+ mSubImages,
+ mSubImageAction);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubListUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubListUiTemplateData.java
new file mode 100644
index 0000000..b5d9645
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubListUiTemplateData.java
@@ -0,0 +1,196 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.smartspace.SmartspaceTarget;
+import android.os.Parcel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds all the relevant data needed to render a Smartspace card with the sub-list Ui Template.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSubListUiTemplateData extends SmartspaceDefaultUiTemplateData {
+
+ @Nullable
+ private final SmartspaceIcon mSubListIcon;
+ @NonNull
+ private final List<CharSequence> mSubListTexts;
+
+ /** Tap action for the sub-list secondary card. */
+ @Nullable
+ private final SmartspaceTapAction mSubListAction;
+
+ SmartspaceSubListUiTemplateData(@NonNull Parcel in) {
+ super(in);
+ mSubListIcon = in.readTypedObject(SmartspaceIcon.CREATOR);
+ mSubListTexts = Arrays.asList(in.readCharSequenceArray());
+ mSubListAction = in.readTypedObject(SmartspaceTapAction.CREATOR);
+ }
+
+ private SmartspaceSubListUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType,
+ @Nullable CharSequence titleText,
+ @Nullable SmartspaceIcon titleIcon,
+ @Nullable CharSequence subtitleText,
+ @Nullable SmartspaceIcon subTitleIcon,
+ @Nullable SmartspaceTapAction primaryTapAction,
+ @Nullable CharSequence supplementalSubtitleText,
+ @Nullable SmartspaceIcon supplementalSubtitleIcon,
+ @Nullable SmartspaceTapAction supplementalSubtitleTapAction,
+ @Nullable CharSequence supplementalAlarmText,
+ @Nullable SmartspaceIcon subListIcon,
+ @NonNull List<CharSequence> subListTexts,
+ @Nullable SmartspaceTapAction subListAction) {
+ super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
+ supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
+ supplementalAlarmText);
+ mSubListIcon = subListIcon;
+ mSubListTexts = subListTexts;
+ mSubListAction = subListAction;
+ }
+
+ @Nullable
+ public SmartspaceIcon getSubListIcon() {
+ return mSubListIcon;
+ }
+
+ @NonNull
+ public List<CharSequence> getSubListTexts() {
+ return mSubListTexts;
+ }
+
+ @Nullable
+ public SmartspaceTapAction getSubListAction() {
+ return mSubListAction;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceSubListUiTemplateData> CREATOR =
+ new Creator<SmartspaceSubListUiTemplateData>() {
+ @Override
+ public SmartspaceSubListUiTemplateData createFromParcel(Parcel in) {
+ return new SmartspaceSubListUiTemplateData(in);
+ }
+
+ @Override
+ public SmartspaceSubListUiTemplateData[] newArray(int size) {
+ return new SmartspaceSubListUiTemplateData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeTypedObject(mSubListIcon, flags);
+ out.writeCharSequenceList(new ArrayList<>(mSubListTexts));
+ out.writeTypedObject(mSubListAction, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceSubListUiTemplateData)) return false;
+ if (!super.equals(o)) return false;
+ SmartspaceSubListUiTemplateData that = (SmartspaceSubListUiTemplateData) o;
+ return Objects.equals(mSubListIcon, that.mSubListIcon) && Objects.equals(
+ mSubListTexts, that.mSubListTexts) && Objects.equals(mSubListAction,
+ that.mSubListAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mSubListIcon, mSubListTexts, mSubListAction);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " + SmartspaceSubListUiTemplateData{"
+ + "mSubListIcon=" + mSubListIcon
+ + ", mSubListTexts=" + mSubListTexts
+ + ", mSubListAction=" + mSubListAction
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceSubListUiTemplateData} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder {
+
+ private SmartspaceIcon mSubListIcon;
+ private final List<CharSequence> mSubListTexts;
+ private SmartspaceTapAction mSubListAction;
+
+ /**
+ * A builder for {@link SmartspaceSubListUiTemplateData}.
+ */
+ public Builder(@NonNull List<CharSequence> subListTexts) {
+ super(SmartspaceTarget.UI_TEMPLATE_SUB_LIST);
+ mSubListTexts = Objects.requireNonNull(subListTexts);
+ }
+
+ /**
+ * Sets the sub-list card icon.
+ */
+ @NonNull
+ public Builder setSubListIcon(@NonNull SmartspaceIcon subListIcon) {
+ mSubListIcon = subListIcon;
+ return this;
+ }
+
+ /**
+ * Sets the card tap action.
+ */
+ @NonNull
+ public Builder setCarouselAction(@NonNull SmartspaceTapAction subListAction) {
+ mSubListAction = subListAction;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceSubListUiTemplateData instance.
+ */
+ @NonNull
+ public SmartspaceSubListUiTemplateData build() {
+ return new SmartspaceSubListUiTemplateData(getTemplateType(), getTitleText(),
+ getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(),
+ getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
+ getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubListIcon,
+ mSubListTexts,
+ mSubListAction);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceTapAction.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceTapAction.java
new file mode 100644
index 0000000..27d8e5f
--- /dev/null
+++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceTapAction.java
@@ -0,0 +1,232 @@
+/*
+ * 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.app.smartspace.uitemplatedata;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.app.smartspace.SmartspaceUtils;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceTapAction} represents an action which can be taken by a user by tapping on
+ * either the title, the subtitle or on the icon. Supported instances are Intents and
+ * PendingIntents. These actions can be called from another process or within the client process.
+ *
+ * Clients can also receive ShorcutInfos in the extras bundle.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTapAction implements Parcelable {
+
+ /** A unique Id of this {@link SmartspaceTapAction}. */
+ @Nullable
+ private final CharSequence mId;
+
+ @Nullable
+ private final Intent mIntent;
+
+ @Nullable
+ private final PendingIntent mPendingIntent;
+
+ @Nullable
+ private final UserHandle mUserHandle;
+
+ @Nullable
+ private Bundle mExtras;
+
+ SmartspaceTapAction(@NonNull Parcel in) {
+ mId = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mIntent = in.readTypedObject(Intent.CREATOR);
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+ mExtras = in.readBundle();
+ }
+
+ private SmartspaceTapAction(@Nullable CharSequence id, @Nullable Intent intent,
+ @Nullable PendingIntent pendingIntent, @Nullable UserHandle userHandle,
+ @Nullable Bundle extras) {
+ mId = id;
+ mIntent = intent;
+ mPendingIntent = pendingIntent;
+ mUserHandle = userHandle;
+ mExtras = extras;
+ }
+
+ @Nullable
+ public CharSequence getId() {
+ return mId;
+ }
+
+ @SuppressLint("IntentBuilderName")
+ @Nullable
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ @Nullable
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ @Nullable
+ public UserHandle getUserHandle() {
+ return mUserHandle;
+ }
+
+ @Nullable
+ @SuppressLint("NullableCollection")
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ TextUtils.writeToParcel(mId, out, flags);
+ out.writeTypedObject(mIntent, flags);
+ out.writeTypedObject(mPendingIntent, flags);
+ out.writeTypedObject(mUserHandle, flags);
+ out.writeBundle(mExtras);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<SmartspaceTapAction> CREATOR = new Creator<SmartspaceTapAction>() {
+ @Override
+ public SmartspaceTapAction createFromParcel(Parcel in) {
+ return new SmartspaceTapAction(in);
+ }
+
+ @Override
+ public SmartspaceTapAction[] newArray(int size) {
+ return new SmartspaceTapAction[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SmartspaceTapAction)) return false;
+ SmartspaceTapAction that = (SmartspaceTapAction) o;
+ return SmartspaceUtils.isEqual(mId, that.mId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId);
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceTapAction{"
+ + "mId=" + mId
+ + "mIntent=" + mIntent
+ + ", mPendingIntent=" + mPendingIntent
+ + ", mUserHandle=" + mUserHandle
+ + ", mExtras=" + mExtras
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SmartspaceTapAction} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ private CharSequence mId;
+ private Intent mIntent;
+ private PendingIntent mPendingIntent;
+ private UserHandle mUserHandle;
+ private Bundle mExtras;
+
+ /**
+ * A builder for {@link SmartspaceTapAction}.
+ *
+ * @param id A unique Id of this {@link SmartspaceTapAction}.
+ */
+ public Builder(@NonNull CharSequence id) {
+ mId = Objects.requireNonNull(id);
+ }
+
+ /**
+ * Sets the action intent.
+ */
+ @NonNull
+ public Builder setIntent(@NonNull Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets the pending intent.
+ */
+ @NonNull
+ public Builder setPendingIntent(@NonNull PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the user handle.
+ */
+ @NonNull
+ @SuppressLint("UserHandleName")
+ public Builder setUserHandle(@Nullable UserHandle userHandle) {
+ mUserHandle = userHandle;
+ return this;
+ }
+
+ /**
+ * Sets the extras.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceTapAction instance.
+ *
+ * @throws IllegalStateException if the tap action is empty.
+ */
+ @NonNull
+ public SmartspaceTapAction build() {
+ if (mIntent == null && mPendingIntent == null && mExtras == null) {
+ throw new IllegalStateException("Please assign at least 1 valid tap field");
+ }
+ return new SmartspaceTapAction(mId, mIntent, mPendingIntent, mUserHandle, mExtras);
+ }
+ }
+}
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/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java
index a36da88..6e3bbcc 100644
--- a/core/java/android/apphibernation/AppHibernationManager.java
+++ b/core/java/android/apphibernation/AppHibernationManager.java
@@ -24,7 +24,10 @@
import android.os.RemoteException;
import android.os.ServiceManager;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* This class provides an API surface for system apps to manipulate the app hibernation
@@ -129,4 +132,38 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Returns the stats from app hibernation for each package provided.
+ *
+ * @param packageNames the set of packages to return stats for
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
+ public @NonNull Map<String, HibernationStats> getHibernationStatsForUser(
+ @NonNull Set<String> packageNames) {
+ try {
+ return mIAppHibernationService.getHibernationStatsForUser(
+ new ArrayList(packageNames), mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the stats from app hibernation for all packages for the user
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
+ public @NonNull Map<String, HibernationStats> getHibernationStatsForUser() {
+ try {
+ return mIAppHibernationService.getHibernationStatsForUser(
+ null /* packageNames */, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/apphibernation/HibernationStats.aidl b/core/java/android/apphibernation/HibernationStats.aidl
new file mode 100644
index 0000000..a92b903
--- /dev/null
+++ b/core/java/android/apphibernation/HibernationStats.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.apphibernation;
+
+parcelable HibernationStats;
\ No newline at end of file
diff --git a/core/java/android/apphibernation/HibernationStats.java b/core/java/android/apphibernation/HibernationStats.java
new file mode 100644
index 0000000..2c4db82
--- /dev/null
+++ b/core/java/android/apphibernation/HibernationStats.java
@@ -0,0 +1,70 @@
+/*
+ * 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.apphibernation;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Stats for a hibernating package.
+ * @hide
+ */
+@SystemApi
+public final class HibernationStats implements Parcelable {
+ private final long mDiskBytesSaved;
+
+ /** @hide */
+ public HibernationStats(long diskBytesSaved) {
+ mDiskBytesSaved = diskBytesSaved;
+ }
+
+ private HibernationStats(@NonNull Parcel in) {
+ mDiskBytesSaved = in.readLong();
+ }
+
+ /**
+ * Get the disk storage saved from hibernation in bytes.
+ */
+ public long getDiskBytesSaved() {
+ return mDiskBytesSaved;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mDiskBytesSaved);
+ }
+
+ public static final @NonNull Creator<HibernationStats> CREATOR =
+ new Creator<HibernationStats>() {
+ @Override
+ public HibernationStats createFromParcel(Parcel in) {
+ return new HibernationStats(in);
+ }
+
+ @Override
+ public HibernationStats[] newArray(int size) {
+ return new HibernationStats[size];
+ }
+ };
+}
diff --git a/core/java/android/apphibernation/IAppHibernationService.aidl b/core/java/android/apphibernation/IAppHibernationService.aidl
index afdb3fe..11bb6b5 100644
--- a/core/java/android/apphibernation/IAppHibernationService.aidl
+++ b/core/java/android/apphibernation/IAppHibernationService.aidl
@@ -16,6 +16,8 @@
package android.apphibernation;
+import android.apphibernation.HibernationStats;
+
/**
* Binder interface to communicate with AppHibernationService.
* @hide
@@ -26,4 +28,6 @@
boolean isHibernatingGlobally(String packageName);
void setHibernatingGlobally(String packageName, boolean isHibernating);
List<String> getHibernatingPackagesForUser(int userId);
+ Map<String, HibernationStats> getHibernationStatsForUser(in List<String> packageNames,
+ int userId);
}
\ No newline at end of file
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/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index a5d97f9..bb88486 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -363,6 +363,9 @@
/**
* The group this permission is a part of, as per
* {@link android.R.attr#permissionGroup}.
+ * <p>
+ * The actual grouping of platform-defined runtime permissions is subject to change and can be
+ * queried with {@link PackageManager#getGroupOfPlatformPermission}.
*/
public @Nullable String group;
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 1aba961..5fd0d84 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -83,7 +83,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.function.Consumer;
/**
* Class for accessing an application's resources. This sits on top of the
@@ -143,9 +142,6 @@
@UnsupportedAppUsage
private DrawableInflater mDrawableInflater;
- /** Used to override the returned adjustments of {@link #getDisplayAdjustments}. */
- private DisplayAdjustments mOverrideDisplayAdjustments;
-
/** Lock object used to protect access to {@link #mTmpValue}. */
private final Object mTmpValueLock = new Object();
@@ -2174,38 +2170,15 @@
/** @hide */
@UnsupportedAppUsage(trackingBug = 176190631)
public DisplayAdjustments getDisplayAdjustments() {
- final DisplayAdjustments overrideDisplayAdjustments = mOverrideDisplayAdjustments;
- if (overrideDisplayAdjustments != null) {
- return overrideDisplayAdjustments;
- }
return mResourcesImpl.getDisplayAdjustments();
}
/**
- * Customize the display adjustments based on the current one in {@link #mResourcesImpl}, in
- * order to isolate the effect with other instances of {@link Resource} that may share the same
- * instance of {@link ResourcesImpl}.
- *
- * @param override The operation to override the existing display adjustments. If it is null,
- * the override adjustments will be cleared.
- * @hide
- */
- public void overrideDisplayAdjustments(@Nullable Consumer<DisplayAdjustments> override) {
- if (override != null) {
- mOverrideDisplayAdjustments = new DisplayAdjustments(
- mResourcesImpl.getDisplayAdjustments());
- override.accept(mOverrideDisplayAdjustments);
- } else {
- mOverrideDisplayAdjustments = null;
- }
- }
-
- /**
* Return {@code true} if the override display adjustments have been set.
* @hide
*/
public boolean hasOverrideDisplayAdjustments() {
- return mOverrideDisplayAdjustments != null;
+ return false;
}
/**
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 7074a2c..79153d7 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -31,6 +31,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.service.SensorPrivacyIndividualEnabledSensorProto;
+import android.service.SensorPrivacySensorProto;
import android.service.SensorPrivacyToggleSourceProto;
import android.util.ArrayMap;
import android.util.Log;
@@ -75,7 +76,7 @@
private final SparseArray<Boolean> mToggleSupportCache = new SparseArray<>();
/**
- * Individual sensors not listed in {@link Sensors}
+ * Sensor constants which are used in {@link SensorPrivacyManager}
*/
public static class Sensors {
@@ -84,12 +85,12 @@
/**
* Constant for the microphone
*/
- public static final int MICROPHONE = SensorPrivacyIndividualEnabledSensorProto.MICROPHONE;
+ public static final int MICROPHONE = SensorPrivacySensorProto.MICROPHONE;
/**
* Constant for the camera
*/
- public static final int CAMERA = SensorPrivacyIndividualEnabledSensorProto.CAMERA;
+ public static final int CAMERA = SensorPrivacySensorProto.CAMERA;
/**
* Individual sensors not listed in {@link Sensors}
@@ -161,6 +162,68 @@
}
/**
+ * Types of toggles which can exist for sensor privacy
+ * @hide
+ */
+ public static class ToggleTypes {
+ private ToggleTypes() {}
+
+ /**
+ * Constant for software toggle.
+ */
+ public static final int SOFTWARE = SensorPrivacyIndividualEnabledSensorProto.SOFTWARE;
+
+ /**
+ * Constant for hardware toggle.
+ */
+ public static final int HARDWARE = SensorPrivacyIndividualEnabledSensorProto.HARDWARE;
+
+ /**
+ * Types of toggles which can exist for sensor privacy
+ *
+ * @hide
+ */
+ @IntDef(value = {
+ SOFTWARE,
+ HARDWARE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ToggleType {}
+
+ }
+
+ /**
+ * Types of state which can exist for the sensor privacy toggle
+ * @hide
+ */
+ public static class StateTypes {
+ private StateTypes() {}
+
+ /**
+ * Constant indicating privacy is enabled.
+ */
+ public static final int ENABLED = SensorPrivacyIndividualEnabledSensorProto.ENABLED;
+
+ /**
+ * Constant indicating privacy is disabled.
+ */
+ public static final int DISABLED = SensorPrivacyIndividualEnabledSensorProto.DISABLED;
+
+ /**
+ * Types of state which can exist for a sensor privacy toggle
+ *
+ * @hide
+ */
+ @IntDef(value = {
+ ENABLED,
+ DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StateType {}
+
+ }
+
+ /**
* A class implementing this interface can register with the {@link
* android.hardware.SensorPrivacyManager} to receive notification when the sensor privacy
* state changes.
@@ -507,7 +570,6 @@
/**
* Don't show dialogs to turn off sensor privacy for this package.
*
- * @param packageName Package name not to show dialogs for
* @param suppress Whether to suppress or re-enable.
*
* @hide
@@ -521,7 +583,6 @@
/**
* Don't show dialogs to turn off sensor privacy for this package.
*
- * @param packageName Package name not to show dialogs for
* @param suppress Whether to suppress or re-enable.
* @param userId the user's id
*
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index 0dc8f92..99f3d15 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -57,6 +57,23 @@
*/
public static final int HIGH_BRIGHTNESS_MODE_HDR = 2;
+ @IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = {
+ BRIGHTNESS_MAX_REASON_NONE,
+ BRIGHTNESS_MAX_REASON_THERMAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BrightnessMaxReason {}
+
+ /**
+ * Maximum brightness is unrestricted.
+ */
+ public static final int BRIGHTNESS_MAX_REASON_NONE = 0;
+
+ /**
+ * Maximum brightness is restricted due to thermal throttling.
+ */
+ public static final int BRIGHTNESS_MAX_REASON_THERMAL = 1;
+
/** Brightness */
public final float brightness;
@@ -78,21 +95,29 @@
*/
public final int highBrightnessMode;
+ /**
+ * The current reason for restricting maximum brightness.
+ * Can be any of BRIGHTNESS_MAX_REASON_* values.
+ */
+ public final int brightnessMaxReason;
+
public BrightnessInfo(float brightness, float brightnessMinimum, float brightnessMaximum,
- @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint) {
+ @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint,
+ @BrightnessMaxReason int brightnessMaxReason) {
this(brightness, brightness, brightnessMinimum, brightnessMaximum, highBrightnessMode,
- highBrightnessTransitionPoint);
+ highBrightnessTransitionPoint, brightnessMaxReason);
}
public BrightnessInfo(float brightness, float adjustedBrightness, float brightnessMinimum,
float brightnessMaximum, @HighBrightnessMode int highBrightnessMode,
- float highBrightnessTransitionPoint) {
+ float highBrightnessTransitionPoint, @BrightnessMaxReason int brightnessMaxReason) {
this.brightness = brightness;
this.adjustedBrightness = adjustedBrightness;
this.brightnessMinimum = brightnessMinimum;
this.brightnessMaximum = brightnessMaximum;
this.highBrightnessMode = highBrightnessMode;
this.highBrightnessTransitionPoint = highBrightnessTransitionPoint;
+ this.brightnessMaxReason = brightnessMaxReason;
}
/**
@@ -110,6 +135,19 @@
return "invalid";
}
+ /**
+ * @return User-friendly string for specified {@link BrightnessMaxReason} parameter.
+ */
+ public static String briMaxReasonToString(@BrightnessMaxReason int reason) {
+ switch (reason) {
+ case BRIGHTNESS_MAX_REASON_NONE:
+ return "none";
+ case BRIGHTNESS_MAX_REASON_THERMAL:
+ return "thermal";
+ }
+ return "invalid";
+ }
+
@Override
public int describeContents() {
return 0;
@@ -123,6 +161,7 @@
dest.writeFloat(brightnessMaximum);
dest.writeInt(highBrightnessMode);
dest.writeFloat(highBrightnessTransitionPoint);
+ dest.writeInt(brightnessMaxReason);
}
public static final @android.annotation.NonNull Creator<BrightnessInfo> CREATOR =
@@ -145,6 +184,7 @@
brightnessMaximum = source.readFloat();
highBrightnessMode = source.readInt();
highBrightnessTransitionPoint = source.readFloat();
+ brightnessMaxReason = source.readInt();
}
}
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/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4fd3751..14f92fb 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -475,7 +475,7 @@
private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations();
@NonNull
- private final NavigationBarController mNavigationBarController =
+ final NavigationBarController mNavigationBarController =
new NavigationBarController(this);
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -2216,10 +2216,12 @@
}
/**
- * Called by the framework to create the layout for showing extacted text.
+ * Called by the framework to create the layout for showing extracted text.
* Only called when in fullscreen mode. The returned view hierarchy must
* have an {@link ExtractEditText} whose ID is
- * {@link android.R.id#inputExtractEditText}.
+ * {@link android.R.id#inputExtractEditText}, with action ID
+ * {@link android.R.id#inputExtractAction} and accessories ID
+ * {@link android.R.id#inputExtractAccessories}.
*/
public View onCreateExtractTextView() {
return mInflater.inflate(
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index a6e475a..d572f5a 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -17,7 +17,10 @@
package android.inputmethodservice;
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;
import android.app.StatusBarManager;
@@ -40,7 +43,10 @@
import android.view.ViewTreeObserver;
import android.view.Window;
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;
@@ -68,6 +74,9 @@
default void onDestroy() {
}
+ default void onSystemBarAppearanceChanged(@Appearance int appearance) {
+ }
+
default String toDebugString() {
return "No-op implementation";
}
@@ -100,11 +109,21 @@
mImpl.onDestroy();
}
+ void onSystemBarAppearanceChanged(@Appearance int appearance) {
+ mImpl.onSystemBarAppearanceChanged(appearance);
+ }
+
String toDebugString() {
return mImpl.toDebugString();
}
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;
@@ -120,6 +139,15 @@
@Nullable
private BroadcastReceiver mSystemOverlayChangedReceiver;
+ @Appearance
+ private int mAppearance;
+
+ @FloatRange(from = 0.0f, to = 1.0f)
+ private float mDarkIntensity;
+
+ @Nullable
+ private ValueAnimator mTintAnimator;
+
Impl(@NonNull InputMethodService inputMethodService) {
mService = inputMethodService;
}
@@ -187,6 +215,8 @@
}
mNavigationBarFrame.setBackground(null);
+
+ setIconTintInternal(calculateTargetDarkIntensity(mAppearance));
}
private void uninstallNavigationBarFrameIfNecessary() {
@@ -353,6 +383,10 @@
if (mDestroyed) {
return;
}
+ if (mTintAnimator != null) {
+ mTintAnimator.cancel();
+ mTintAnimator = null;
+ }
if (mSystemOverlayChangedReceiver != null) {
mService.unregisterReceiver(mSystemOverlayChangedReceiver);
mSystemOverlayChangedReceiver = null;
@@ -389,9 +423,56 @@
}
@Override
+ public void onSystemBarAppearanceChanged(@Appearance int appearance) {
+ if (mDestroyed) {
+ return;
+ }
+
+ mAppearance = appearance;
+
+ if (mNavigationBarFrame == null) {
+ return;
+ }
+
+ final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance);
+
+ 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) {
+ return;
+ }
+ navigationBarView.setDarkIntensity(darkIntensity);
+ }
+
+ @FloatRange(from = 0.0f, to = 1.0f)
+ private static float calculateTargetDarkIntensity(@Appearance int appearance) {
+ final boolean lightNavBar = (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
+ return lightNavBar ? 1.0f : 0.0f;
+ }
+
+ @Override
public String toDebugString() {
return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons
+ " mNavigationBarFrame=" + mNavigationBarFrame
+ + " mAppearance=0x" + Integer.toHexString(mAppearance)
+ + " mDarkIntensity=" + mDarkIntensity
+ "}";
}
}
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 6c8eb41..0893d2a 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -23,7 +23,6 @@
import android.annotation.IntDef;
import android.app.Dialog;
-import android.content.Context;
import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
@@ -32,6 +31,7 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
+import android.view.WindowInsetsController;
import android.view.WindowManager;
import java.lang.annotation.Retention;
@@ -47,6 +47,7 @@
private final KeyEvent.DispatcherState mDispatcherState;
private final Rect mBounds = new Rect();
+ private final InputMethodService mService;
@Retention(SOURCE)
@IntDef(value = {WindowState.TOKEN_PENDING, WindowState.TOKEN_SET,
@@ -120,7 +121,7 @@
/**
* Create a SoftInputWindow that uses a custom style.
*
- * @param context The Context in which the DockWindow should run. In
+ * @param service The {@link InputMethodService} in which the DockWindow should run. In
* particular, it uses the window manager and theme from this context
* to present its UI.
* @param theme A style resource describing the theme to use for the window.
@@ -129,8 +130,10 @@
* using styles. This theme is applied on top of the current theme in
* <var>context</var>. If 0, the default dialog theme will be used.
*/
- SoftInputWindow(Context context, int theme, KeyEvent.DispatcherState dispatcherState) {
- super(context, theme);
+ SoftInputWindow(InputMethodService service, int theme,
+ KeyEvent.DispatcherState dispatcherState) {
+ super(service, theme);
+ mService = service;
mDispatcherState = dispatcherState;
}
@@ -261,6 +264,11 @@
}
}
+ @Override
+ public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
+ mService.mNavigationBarController.onSystemBarAppearanceChanged(appearance);
+ }
+
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
mBounds.dumpDebug(proto, BOUNDS);
diff --git a/core/java/android/inputmethodservice/navigationbar/DeadZone.java b/core/java/android/inputmethodservice/navigationbar/DeadZone.java
index cd85736..4adc84b 100644
--- a/core/java/android/inputmethodservice/navigationbar/DeadZone.java
+++ b/core/java/android/inputmethodservice/navigationbar/DeadZone.java
@@ -27,6 +27,7 @@
import android.content.res.Resources;
import android.graphics.Canvas;
import android.os.SystemClock;
+import android.util.FloatProperty;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
@@ -46,6 +47,20 @@
public static final int VERTICAL = 1; // Consume taps along the left edge.
private static final boolean CHATTY = true; // print to logcat when we eat a click
+
+ private static final FloatProperty<DeadZone> FLASH_PROPERTY =
+ new FloatProperty<DeadZone>("DeadZoneFlash") {
+ @Override
+ public void setValue(DeadZone object, float value) {
+ object.setFlash(value);
+ }
+
+ @Override
+ public Float get(DeadZone object) {
+ return object.getFlash();
+ }
+ };
+
private final NavigationBarView mNavigationBarView;
private boolean mShouldFlash;
@@ -63,7 +78,7 @@
private final Runnable mDebugFlash = new Runnable() {
@Override
public void run() {
- ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start();
+ ObjectAnimator.ofFloat(DeadZone.this, FLASH_PROPERTY, 1f, 0f).setDuration(150).start();
}
};
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 47a272c..de76c8f 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -673,6 +673,7 @@
public final int firstCustomConsumedPowerColumn;
public final int firstCustomUsageDurationColumn;
public final int columnCount;
+ public final Key[][] processStateKeys;
private BatteryConsumerDataLayout(int firstColumn, String[] customPowerComponentNames,
boolean powerModelsIncluded, boolean includeProcessStateData) {
@@ -728,6 +729,28 @@
keys[componentId] = perComponentKeys.toArray(KEY_ARRAY);
}
+ if (includeProcessStateData) {
+ processStateKeys = new Key[BatteryConsumer.PROCESS_STATE_COUNT][];
+ ArrayList<Key> perProcStateKeys = new ArrayList<>();
+ for (int processState = 0; processState < PROCESS_STATE_COUNT; processState++) {
+ if (processState == PROCESS_STATE_UNSPECIFIED) {
+ continue;
+ }
+
+ perProcStateKeys.clear();
+ for (int i = 0; i < keys.length; i++) {
+ for (int j = 0; j < keys[i].length; j++) {
+ if (keys[i][j].processState == processState) {
+ perProcStateKeys.add(keys[i][j]);
+ }
+ }
+ }
+ processStateKeys[processState] = perProcStateKeys.toArray(KEY_ARRAY);
+ }
+ } else {
+ processStateKeys = null;
+ }
+
firstCustomConsumedPowerColumn = columnIndex;
columnIndex += customPowerComponentCount;
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/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 590494c..48e1116 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -16,7 +16,6 @@
package android.os;
import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
-import static android.os.BatteryConsumer.POWER_COMPONENT_COUNT;
import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
@@ -60,19 +59,16 @@
return mData.getDouble(mData.getKeyOrThrow(dimensions.powerComponent,
dimensions.processState).mPowerColumnIndex);
} else if (dimensions.processState != PROCESS_STATE_ANY) {
- boolean foundSome = false;
- double totalPowerMah = 0;
- for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) {
- BatteryConsumer.Key key = mData.getKey(componentId, dimensions.processState);
- if (key != null) {
- foundSome = true;
- totalPowerMah += mData.getDouble(key.mPowerColumnIndex);
- }
- }
- if (!foundSome) {
+ if (!mData.layout.processStateDataIncluded) {
throw new IllegalArgumentException(
"No data included in BatteryUsageStats for " + dimensions);
}
+ final BatteryConsumer.Key[] keys =
+ mData.layout.processStateKeys[dimensions.processState];
+ double totalPowerMah = 0;
+ for (int i = keys.length - 1; i >= 0; i--) {
+ totalPowerMah += mData.getDouble(keys[i].mPowerColumnIndex);
+ }
return totalPowerMah;
} else {
return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex);
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/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 8df659d..63616da 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -291,6 +291,8 @@
public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10;
/** {@hide} */
public static final int FLAG_INCLUDE_RECENT = 1 << 11;
+ /** {@hide} */
+ public static final int FLAG_INCLUDE_SHARED_PROFILE = 1 << 12;
/** {@hide} */
public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
@@ -1328,6 +1330,23 @@
}
/**
+ * Return the list of shared/external storage volumes currently available to
+ * the calling user and the user it shares media with
+ * CDD link : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support
+ * <p>
+ * This is similar to {@link StorageManager#getStorageVolumes()} except that the result also
+ * includes the volumes belonging to any user it shares media with
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+ public @NonNull List<StorageVolume> getStorageVolumesIncludingSharedProfiles() {
+ final ArrayList<StorageVolume> res = new ArrayList<>();
+ Collections.addAll(res,
+ getVolumeList(mContext.getUserId(),
+ FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_SHARED_PROFILE));
+ return res;
+ }
+
+ /**
* Return the list of shared/external storage volumes both currently and
* recently available to the calling user.
* <p>
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 8ee52c2..e1f112a 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -16,8 +16,6 @@
package android.os.storage;
-import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -308,11 +306,9 @@
/**
* Returns the user that owns this volume
- *
- * {@hide}
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- @SystemApi(client = MODULE_LIBRARIES)
+ // TODO(b/193460475) : Android Lint handle API change from systemApi to public Api incorrectly
+ @SuppressLint("NewApi")
public @NonNull UserHandle getOwner() {
return mOwner;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2a563ac..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.
@@ -6574,6 +6583,8 @@
* @hide
*/
@Readable(maxTargetSdk = Build.VERSION_CODES.S)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLUETOOTH_NAME = "bluetooth_name";
/**
@@ -6581,6 +6592,8 @@
* @hide
*/
@Readable(maxTargetSdk = Build.VERSION_CODES.S)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLUETOOTH_ADDRESS = "bluetooth_address";
/**
@@ -6588,6 +6601,8 @@
* @hide
*/
@Readable(maxTargetSdk = Build.VERSION_CODES.S)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid";
/**
@@ -10259,6 +10274,13 @@
public static final String NEARBY_SHARING_COMPONENT = "nearby_sharing_component";
/**
+ * Nearby Sharing Slice URI for the SliceProvider to
+ * read Nearby Sharing scan results and then draw the UI.
+ * @hide
+ */
+ public static final String NEARBY_SHARING_SLICE_URI = "nearby_sharing_slice_uri";
+
+ /**
* Controls whether aware is enabled.
* @hide
*/
@@ -10826,6 +10848,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device";
/**
@@ -10834,6 +10858,8 @@
* {@hide}
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLUETOOTH_DISABLED_PROFILES = "bluetooth_disabled_profiles";
/**
@@ -12371,6 +12397,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled";
/**
@@ -12378,6 +12406,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_LOW_POWER_WINDOW_MS = "ble_scan_low_power_window_ms";
/**
@@ -12385,6 +12415,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_BALANCED_WINDOW_MS = "ble_scan_balanced_window_ms";
/**
@@ -12392,6 +12424,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_LOW_LATENCY_WINDOW_MS =
"ble_scan_low_latency_window_ms";
@@ -12400,6 +12434,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_LOW_POWER_INTERVAL_MS =
"ble_scan_low_power_interval_ms";
@@ -12408,6 +12444,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_BALANCED_INTERVAL_MS =
"ble_scan_balanced_interval_ms";
@@ -12416,6 +12454,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_LOW_LATENCY_INTERVAL_MS =
"ble_scan_low_latency_interval_ms";
@@ -12424,6 +12464,8 @@
* @hide
*/
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String BLE_SCAN_BACKGROUND_MODE = "ble_scan_background_mode";
/**
@@ -13156,6 +13198,8 @@
/** {@hide} */
@Readable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("NoSettingsProvider")
public static final String
BLUETOOTH_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode";
/** {@hide} */
@@ -16706,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/Display.java b/core/java/android/view/Display.java
index d6e074f..fa39380 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -115,14 +115,6 @@
private int mCachedAppHeightCompat;
/**
- * Indicates that the application is started in a different rotation than the real display, so
- * the display information may be adjusted. That ensures the methods {@link #getRotation},
- * {@link #getRealSize}, {@link #getRealMetrics}, and {@link #getCutout} are consistent with how
- * the application window is laid out.
- */
- private boolean mMayAdjustByFixedRotation;
-
- /**
* Cache if the application is the recents component.
* TODO(b/179308296) Remove once Launcher addresses issue
*/
@@ -916,14 +908,15 @@
* degrees counter-clockwise, to compensate rendering will be rotated by
* 90 degrees clockwise and thus the returned value here will be
* {@link Surface#ROTATION_90 Surface.ROTATION_90}.
+ *
+ * This rotation value will match the results of {@link #getMetrics}: this means that the
+ * rotation value will correspond to the activity if accessed through the activity.
*/
@Surface.Rotation
public int getRotation() {
synchronized (mLock) {
updateDisplayInfoLocked();
- return mMayAdjustByFixedRotation
- ? getDisplayAdjustments().getRotation(mDisplayInfo.rotation)
- : mDisplayInfo.rotation;
+ return getLocalRotation();
}
}
@@ -959,9 +952,15 @@
public DisplayCutout getCutout() {
synchronized (mLock) {
updateDisplayInfoLocked();
- return mMayAdjustByFixedRotation
- ? getDisplayAdjustments().getDisplayCutout(mDisplayInfo.displayCutout)
- : mDisplayInfo.displayCutout;
+ if (mResources == null) return mDisplayInfo.displayCutout;
+ final DisplayCutout localCutout = mDisplayInfo.displayCutout;
+ if (localCutout == null) return null;
+ int rotation = getLocalRotation();
+ if (rotation != mDisplayInfo.rotation) {
+ return localCutout.getRotated(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
+ mDisplayInfo.rotation, rotation);
+ }
+ return localCutout;
}
}
@@ -977,15 +976,11 @@
public RoundedCorner getRoundedCorner(@RoundedCorner.Position int position) {
synchronized (mLock) {
updateDisplayInfoLocked();
- RoundedCorners roundedCorners;
- if (mMayAdjustByFixedRotation) {
- roundedCorners = getDisplayAdjustments().adjustRoundedCorner(
- mDisplayInfo.roundedCorners,
- mDisplayInfo.rotation,
- mDisplayInfo.logicalWidth,
- mDisplayInfo.logicalHeight);
- } else {
- roundedCorners = mDisplayInfo.roundedCorners;
+ final RoundedCorners roundedCorners = mDisplayInfo.roundedCorners;
+ final @Surface.Rotation int rotation = getLocalRotation();
+ if (roundedCorners != null && rotation != mDisplayInfo.rotation) {
+ roundedCorners.rotate(rotation,
+ mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
}
return roundedCorners == null ? null : roundedCorners.getRoundedCorner(position);
}
@@ -1468,8 +1463,9 @@
}
outSize.x = mDisplayInfo.logicalWidth;
outSize.y = mDisplayInfo.logicalHeight;
- if (mMayAdjustByFixedRotation) {
- getDisplayAdjustments().adjustSize(outSize, mDisplayInfo.rotation);
+ final @Surface.Rotation int rotation = getLocalRotation();
+ if (rotation != mDisplayInfo.rotation) {
+ adjustSize(outSize, mDisplayInfo.rotation, rotation);
}
}
}
@@ -1537,8 +1533,9 @@
}
mDisplayInfo.getLogicalMetrics(outMetrics,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
- if (mMayAdjustByFixedRotation) {
- getDisplayAdjustments().adjustMetrics(outMetrics, mDisplayInfo.rotation);
+ final @Surface.Rotation int rotation = getLocalRotation();
+ if (rotation != mDisplayInfo.rotation) {
+ adjustMetrics(outMetrics, mDisplayInfo.rotation, rotation);
}
}
}
@@ -1663,9 +1660,6 @@
}
}
}
-
- mMayAdjustByFixedRotation = mResources != null
- && mResources.hasOverrideDisplayAdjustments();
}
private void updateCachedAppSizeIfNeededLocked() {
@@ -1679,6 +1673,49 @@
}
}
+ /** Returns {@code false} if the width and height of display should swap. */
+ private static boolean noFlip(@Surface.Rotation int realRotation,
+ @Surface.Rotation int localRotation) {
+ // Check if the delta is rotated by 90 degrees.
+ return (realRotation - localRotation + 4) % 2 == 0;
+ }
+
+ /**
+ * Adjusts the given size by a rotation offset if necessary.
+ * @hide
+ */
+ private void adjustSize(@NonNull Point size, @Surface.Rotation int realRotation,
+ @Surface.Rotation int localRotation) {
+ if (noFlip(realRotation, localRotation)) return;
+ final int w = size.x;
+ size.x = size.y;
+ size.y = w;
+ }
+
+ /**
+ * Adjusts the given metrics by a rotation offset if necessary.
+ * @hide
+ */
+ private void adjustMetrics(@NonNull DisplayMetrics metrics,
+ @Surface.Rotation int realRotation, @Surface.Rotation int localRotation) {
+ if (noFlip(realRotation, localRotation)) return;
+ int w = metrics.widthPixels;
+ metrics.widthPixels = metrics.heightPixels;
+ metrics.heightPixels = w;
+
+ w = metrics.noncompatWidthPixels;
+ metrics.noncompatWidthPixels = metrics.noncompatHeightPixels;
+ metrics.noncompatHeightPixels = w;
+ }
+
+ private @Surface.Rotation int getLocalRotation() {
+ if (mResources == null) return mDisplayInfo.rotation;
+ final @Surface.Rotation int localRotation =
+ mResources.getConfiguration().windowConfiguration.getDisplayRotation();
+ if (localRotation != WindowConfiguration.ROTATION_UNDEFINED) return localRotation;
+ return mDisplayInfo.rotation;
+ }
+
// For debugging purposes
@Override
public String toString() {
@@ -1686,9 +1723,7 @@
updateDisplayInfoLocked();
final DisplayAdjustments adjustments = getDisplayAdjustments();
mDisplayInfo.getAppMetrics(mTempMetrics, adjustments);
- return "Display id " + mDisplayId + ": " + mDisplayInfo
- + (mMayAdjustByFixedRotation
- ? (", " + adjustments.getFixedRotationAdjustments() + ", ") : ", ")
+ return "Display id " + mDisplayId + ": " + mDisplayInfo + ", "
+ mTempMetrics + ", isValid=" + mIsValid;
}
}
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
index e307eff..bb50849 100644
--- a/core/java/android/view/DisplayAdjustments.java
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -21,10 +21,6 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.graphics.Point;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.DisplayMetrics;
import java.util.Objects;
@@ -34,7 +30,6 @@
private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
private final Configuration mConfiguration = new Configuration(Configuration.EMPTY);
- private FixedRotationAdjustments mFixedRotationAdjustments;
@UnsupportedAppUsage
public DisplayAdjustments() {
@@ -49,7 +44,6 @@
public DisplayAdjustments(@NonNull DisplayAdjustments daj) {
setCompatibilityInfo(daj.mCompatInfo);
mConfiguration.setTo(daj.getConfiguration());
- mFixedRotationAdjustments = daj.mFixedRotationAdjustments;
}
@UnsupportedAppUsage
@@ -90,97 +84,11 @@
return mConfiguration;
}
- public void setFixedRotationAdjustments(FixedRotationAdjustments fixedRotationAdjustments) {
- mFixedRotationAdjustments = fixedRotationAdjustments;
- }
-
- public FixedRotationAdjustments getFixedRotationAdjustments() {
- return mFixedRotationAdjustments;
- }
-
- /** Returns {@code false} if the width and height of display should swap. */
- private boolean noFlip(@Surface.Rotation int realRotation) {
- final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
- if (rotationAdjustments == null) {
- return true;
- }
- // Check if the delta is rotated by 90 degrees.
- return (realRotation - rotationAdjustments.mRotation + 4) % 2 == 0;
- }
-
- /** Adjusts the given size if possible. */
- public void adjustSize(@NonNull Point size, @Surface.Rotation int realRotation) {
- if (noFlip(realRotation)) {
- return;
- }
- final int w = size.x;
- size.x = size.y;
- size.y = w;
- }
-
- /** Adjusts the given metrics if possible. */
- public void adjustMetrics(@NonNull DisplayMetrics metrics, @Surface.Rotation int realRotation) {
- if (noFlip(realRotation)) {
- return;
- }
- int w = metrics.widthPixels;
- metrics.widthPixels = metrics.heightPixels;
- metrics.heightPixels = w;
-
- w = metrics.noncompatWidthPixels;
- metrics.noncompatWidthPixels = metrics.noncompatHeightPixels;
- metrics.noncompatHeightPixels = w;
- }
-
- /** Adjusts global display metrics that is available to applications. */
- public void adjustGlobalAppMetrics(@NonNull DisplayMetrics metrics) {
- final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
- if (rotationAdjustments == null) {
- return;
- }
- metrics.noncompatWidthPixels = metrics.widthPixels = rotationAdjustments.mAppWidth;
- metrics.noncompatHeightPixels = metrics.heightPixels = rotationAdjustments.mAppHeight;
- }
-
- /** Returns the adjusted cutout if available. Otherwise the original cutout is returned. */
- @Nullable
- public DisplayCutout getDisplayCutout(@Nullable DisplayCutout realCutout) {
- final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
- return rotationAdjustments != null && rotationAdjustments.mRotatedDisplayCutout != null
- ? rotationAdjustments.mRotatedDisplayCutout
- : realCutout;
- }
-
- /**
- * Returns the adjusted {@link RoundedCorners} if available. Otherwise the original
- * {@link RoundedCorners} is returned.
- */
- @Nullable
- public RoundedCorners adjustRoundedCorner(@Nullable RoundedCorners realRoundedCorners,
- @Surface.Rotation int realRotation, int displayWidth, int displayHeight) {
- final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
- if (realRoundedCorners == null || rotationAdjustments == null
- || rotationAdjustments.mRotation == realRotation) {
- return realRoundedCorners;
- }
-
- return realRoundedCorners.rotate(
- rotationAdjustments.mRotation, displayWidth, displayHeight);
- }
-
- /** Returns the adjusted rotation if available. Otherwise the original rotation is returned. */
- @Surface.Rotation
- public int getRotation(@Surface.Rotation int realRotation) {
- final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
- return rotationAdjustments != null ? rotationAdjustments.mRotation : realRotation;
- }
-
@Override
public int hashCode() {
int hash = 17;
hash = hash * 31 + Objects.hashCode(mCompatInfo);
hash = hash * 31 + Objects.hashCode(mConfiguration);
- hash = hash * 31 + Objects.hashCode(mFixedRotationAdjustments);
return hash;
}
@@ -191,101 +99,6 @@
}
DisplayAdjustments daj = (DisplayAdjustments)o;
return Objects.equals(daj.mCompatInfo, mCompatInfo)
- && Objects.equals(daj.mConfiguration, mConfiguration)
- && Objects.equals(daj.mFixedRotationAdjustments, mFixedRotationAdjustments);
- }
-
- /**
- * An application can be launched in different rotation than the real display. This class
- * provides the information to adjust the values returned by {@link Display}.
- * @hide
- */
- public static class FixedRotationAdjustments implements Parcelable {
- /** The application-based rotation. */
- @Surface.Rotation
- final int mRotation;
-
- /**
- * The rotated {@link DisplayInfo#appWidth}. The value cannot be simply swapped according
- * to rotation because it minus the region of screen decorations.
- */
- final int mAppWidth;
-
- /** The rotated {@link DisplayInfo#appHeight}. */
- final int mAppHeight;
-
- /** Non-null if the device has cutout. */
- @Nullable
- final DisplayCutout mRotatedDisplayCutout;
-
- public FixedRotationAdjustments(@Surface.Rotation int rotation, int appWidth, int appHeight,
- DisplayCutout cutout) {
- mRotation = rotation;
- mAppWidth = appWidth;
- mAppHeight = appHeight;
- mRotatedDisplayCutout = cutout;
- }
-
- @Override
- public int hashCode() {
- int hash = 17;
- hash = hash * 31 + mRotation;
- hash = hash * 31 + mAppWidth;
- hash = hash * 31 + mAppHeight;
- hash = hash * 31 + Objects.hashCode(mRotatedDisplayCutout);
- return hash;
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (!(o instanceof FixedRotationAdjustments)) {
- return false;
- }
- final FixedRotationAdjustments other = (FixedRotationAdjustments) o;
- return mRotation == other.mRotation
- && mAppWidth == other.mAppWidth && mAppHeight == other.mAppHeight
- && Objects.equals(mRotatedDisplayCutout, other.mRotatedDisplayCutout);
- }
-
- @Override
- public String toString() {
- return "FixedRotationAdjustments{rotation=" + Surface.rotationToString(mRotation)
- + " appWidth=" + mAppWidth + " appHeight=" + mAppHeight
- + " cutout=" + mRotatedDisplayCutout + "}";
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mRotation);
- dest.writeInt(mAppWidth);
- dest.writeInt(mAppHeight);
- dest.writeTypedObject(
- new DisplayCutout.ParcelableWrapper(mRotatedDisplayCutout), flags);
- }
-
- private FixedRotationAdjustments(Parcel in) {
- mRotation = in.readInt();
- mAppWidth = in.readInt();
- mAppHeight = in.readInt();
- final DisplayCutout.ParcelableWrapper cutoutWrapper =
- in.readTypedObject(DisplayCutout.ParcelableWrapper.CREATOR);
- mRotatedDisplayCutout = cutoutWrapper != null ? cutoutWrapper.get() : null;
- }
-
- public static final Creator<FixedRotationAdjustments> CREATOR =
- new Creator<FixedRotationAdjustments>() {
- public FixedRotationAdjustments createFromParcel(Parcel in) {
- return new FixedRotationAdjustments(in);
- }
-
- public FixedRotationAdjustments[] newArray(int size) {
- return new FixedRotationAdjustments[size];
- }
- };
+ && Objects.equals(daj.mConfiguration, mConfiguration);
}
}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index b461faf..7e0d887 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -26,6 +26,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.view.accessibility.IAccessibilityEmbeddedConnection;
import android.view.InsetsState;
@@ -180,6 +181,36 @@
return mRemoteInterface;
}
+ /**
+ * Forward a configuration to the remote SurfaceControlViewHost.
+ * This will cause View#onConfigurationChanged to be invoked on the remote
+ * end. This does not automatically cause the SurfaceControlViewHost
+ * to be resized. The root View of a SurfaceControlViewHost
+ * is more akin to a PopupWindow in that the size is user specified
+ * independent of configuration width and height.
+ *
+ * @param c The configuration to forward
+ */
+ public void notifyConfigurationChanged(@NonNull Configuration c) {
+ try {
+ getRemoteInterface().onConfigurationChanged(c);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Tear down the remote SurfaceControlViewHost and cause
+ * View#onDetachedFromWindow to be invoked on the other side.
+ */
+ public void notifyDetachedFromWindow() {
+ try {
+ getRemoteInterface().onDispatchDetachedFromWindow();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
@Override
public int describeContents() {
return 0;
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/Window.java b/core/java/android/view/Window.java
index aa9ea19..039b50a 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -607,6 +607,17 @@
* @param hasCapture True if the window has pointer capture.
*/
default public void onPointerCaptureChanged(boolean hasCapture) { };
+
+ /**
+ * Called from
+ * {@link com.android.internal.policy.DecorView#onSystemBarAppearanceChanged(int)}.
+ *
+ * @param appearance The newly applied appearance.
+ * @hide
+ */
+ default void onSystemBarAppearanceChanged(
+ @WindowInsetsController.Appearance int appearance) {
+ }
}
/** @hide */
diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java
index 02c8945..115e9e8 100644
--- a/core/java/android/view/WindowCallbackWrapper.java
+++ b/core/java/android/view/WindowCallbackWrapper.java
@@ -163,5 +163,10 @@
public void onPointerCaptureChanged(boolean hasCapture) {
mWrapped.onPointerCaptureChanged(hasCapture);
}
+
+ @Override
+ public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
+ mWrapped.onSystemBarAppearanceChanged(appearance);
+ }
}
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/ConfigurationHelper.java b/core/java/android/window/ConfigurationHelper.java
index 9a07975..3a3eb74 100644
--- a/core/java/android/window/ConfigurationHelper.java
+++ b/core/java/android/window/ConfigurationHelper.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ResourcesManager;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -99,6 +100,10 @@
if (shouldUpdateWindowMetricsBounds(config, newConfig)) {
return true;
}
+ // If the display rotation has changed, we also need to update resources.
+ if (isDisplayRotationChanged(config, newConfig)) {
+ return true;
+ }
return configChanged == null ? config.diff(newConfig) != 0 : configChanged;
}
@@ -129,4 +134,15 @@
return !currentBounds.equals(newBounds) || !currentMaxBounds.equals(newMaxBounds);
}
+
+ private static boolean isDisplayRotationChanged(@NonNull Configuration config,
+ @NonNull Configuration newConfig) {
+ final int origRot = config.windowConfiguration.getDisplayRotation();
+ final int newRot = newConfig.windowConfiguration.getDisplayRotation();
+ if (newRot == WindowConfiguration.ROTATION_UNDEFINED
+ || origRot == WindowConfiguration.ROTATION_UNDEFINED) {
+ return false;
+ }
+ return origRot != newRot;
+ }
}
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/BlockedAppActivity.java b/core/java/com/android/internal/app/BlockedAppActivity.java
index fbdbbfb..65526eb 100644
--- a/core/java/com/android/internal/app/BlockedAppActivity.java
+++ b/core/java/com/android/internal/app/BlockedAppActivity.java
@@ -17,6 +17,7 @@
package com.android.internal.app;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
@@ -35,6 +36,9 @@
private static final String TAG = "BlockedAppActivity";
private static final String PACKAGE_NAME = "com.android.internal.app";
private static final String EXTRA_BLOCKED_PACKAGE = PACKAGE_NAME + ".extra.BLOCKED_PACKAGE";
+ private static final String EXTRA_BLOCKED_ACTIVITY_INFO =
+ PACKAGE_NAME + ".extra.BLOCKED_ACTIVITY_INFO";
+ private static final String EXTRA_STREAMED_DEVICE = PACKAGE_NAME + ".extra.STREAMED_DEVICE";
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -48,17 +52,30 @@
return;
}
+ CharSequence appLabel = null;
String packageName = intent.getStringExtra(EXTRA_BLOCKED_PACKAGE);
- if (TextUtils.isEmpty(packageName)) {
- Slog.wtf(TAG, "Invalid package: " + packageName);
+ ActivityInfo activityInfo = intent.getParcelableExtra(EXTRA_BLOCKED_ACTIVITY_INFO);
+ if (activityInfo != null) {
+ appLabel = activityInfo.loadLabel(getPackageManager());
+ } else if (!TextUtils.isEmpty(packageName)) {
+ appLabel = getAppLabel(userId, packageName);
+ }
+
+ if (TextUtils.isEmpty(appLabel)) {
+ Slog.wtf(TAG, "Invalid package: " + packageName + " or activity info: " + activityInfo);
finish();
return;
}
- CharSequence appLabel = getAppLabel(userId, packageName);
-
- mAlertParams.mTitle = getString(R.string.app_blocked_title);
- mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
+ CharSequence streamedDeviceName = intent.getCharSequenceExtra(EXTRA_STREAMED_DEVICE);
+ if (!TextUtils.isEmpty(streamedDeviceName)) {
+ mAlertParams.mTitle = getString(R.string.app_streaming_blocked_title, appLabel);
+ mAlertParams.mMessage =
+ getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+ } else {
+ mAlertParams.mTitle = getString(R.string.app_blocked_title);
+ mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
+ }
mAlertParams.mPositiveButtonText = getString(android.R.string.ok);
setupAlert();
}
@@ -83,4 +100,19 @@
.putExtra(Intent.EXTRA_USER_ID, userId)
.putExtra(EXTRA_BLOCKED_PACKAGE, packageName);
}
+
+ /**
+ * Creates an intent that launches {@link BlockedAppActivity} when app streaming is blocked.
+ *
+ * Using this method and providing a non-empty {@code streamedDeviceName} will cause the dialog
+ * to use streaming-specific error messages.
+ */
+ public static Intent createStreamingBlockedIntent(int userId, ActivityInfo activityInfo,
+ CharSequence streamedDeviceName) {
+ return new Intent()
+ .setClassName("android", BlockedAppActivity.class.getName())
+ .putExtra(Intent.EXTRA_USER_ID, userId)
+ .putExtra(EXTRA_BLOCKED_ACTIVITY_INFO, activityInfo)
+ .putExtra(EXTRA_STREAMED_DEVICE, streamedDeviceName);
+ }
}
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/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index d7eeb7b..a50282e 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1034,6 +1034,12 @@
@Override
public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) {
updateColorViews(null /* insets */, true /* animate */);
+ if (mWindow != null) {
+ final Window.Callback callback = mWindow.getCallback();
+ if (callback != null) {
+ callback.onSystemBarAppearanceChanged(appearance);
+ }
+ }
}
@Override
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/OWNERS b/core/jni/OWNERS
index 9a460f5..24c0d2a 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -66,6 +66,7 @@
### Graphics ###
per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS
+per-file android_hardware_HardwareBuffer.cpp = file:/graphics/java/android/graphics/OWNERS
### Text ###
per-file android_text_* = file:/core/java/android/text/OWNERS
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/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto
index 81d849e..97870a1 100644
--- a/core/proto/android/hardware/sensorprivacy.proto
+++ b/core/proto/android/hardware/sensorprivacy.proto
@@ -22,6 +22,13 @@
import "frameworks/base/core/proto/android/privacy.proto";
+message AllSensorPrivacyServiceDumpProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Is global sensor privacy enabled
+ optional bool is_enabled = 1;
+}
+
message SensorPrivacyServiceDumpProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -35,6 +42,9 @@
// Per user settings for sensor privacy
repeated SensorPrivacyUserProto user = 3;
+
+ // Implementation
+ optional string storage_implementation = 4;
}
message SensorPrivacyUserProto {
@@ -43,16 +53,47 @@
// User id
optional int32 user_id = 1;
+ // DEPRECATED
// Is global sensor privacy enabled
optional bool is_enabled = 2;
// Per sensor privacy enabled
+ // DEPRECATED
repeated SensorPrivacyIndividualEnabledSensorProto individual_enabled_sensor = 3;
+
+ // Per toggle type sensor privacy
+ repeated SensorPrivacySensorProto sensors = 4;
+}
+
+message SensorPrivacySensorProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ enum Sensor {
+ UNKNOWN = 0;
+
+ MICROPHONE = 1;
+ CAMERA = 2;
+ }
+
+ optional int32 sensor = 1;
+
+ repeated SensorPrivacyIndividualEnabledSensorProto toggles = 2;
}
message SensorPrivacyIndividualEnabledSensorProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
+ enum ToggleType {
+ SOFTWARE = 1;
+ HARDWARE = 2;
+ }
+
+ enum StateType {
+ ENABLED = 1;
+ DISABLED = 2;
+ }
+
+ // DEPRECATED
enum Sensor {
UNKNOWN = 0;
@@ -63,8 +104,17 @@
// Sensor for which privacy might be enabled
optional Sensor sensor = 1;
- // If sensor privacy is enabled for this sensor
+ // DEPRECATED
optional bool is_enabled = 2;
+
+ // Timestamp of the last time the sensor was changed
+ optional int64 last_change = 3;
+
+ // The toggle type for this state
+ optional ToggleType toggle_type = 4;
+
+ // If sensor privacy state for this sensor
+ optional StateType state_type = 5;
}
message SensorPrivacyToggleSourceProto {
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 ff04339..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. -->
@@ -1838,11 +1839,12 @@
<permission android:name="android.permission.ACCESS_MOCK_LOCATION"
android:protectionLevel="signature" />
- <!-- @SystemApi @hide Allows automotive applications to control location
+ <!-- @hide @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
+ Allows automotive applications to control location
suspend state for power management use cases.
<p>Not for use by third-party applications.
-->
- <permission android:name="android.permission.AUTOMOTIVE_GNSS_CONTROLS"
+ <permission android:name="android.permission.CONTROL_AUTOMOTIVE_GNSS"
android:protectionLevel="signature|privileged" />
<!-- ======================================= -->
@@ -3661,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
@@ -4955,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" />
@@ -5402,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/ids.xml b/core/res/res/values/ids.xml
index db348ed..bccd2b6 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -59,6 +59,12 @@
<item type="id" name="candidatesArea" />
<item type="id" name="inputArea" />
<item type="id" name="inputExtractEditText" />
+ <!-- View id for the action of text editor inside of an extracted text
+ {@link InputMethodService#onCreateExtractTextView IME extract view}. -->
+ <item type="id" name="inputExtractAction" />
+ <!-- View id for the accessories of text editor inside of an extracted text
+ {@link InputMethodService#onCreateExtractTextView IME extract view}. -->
+ <item type="id" name="inputExtractAccessories" />
<item type="id" name="selectAll" />
<item type="id" name="cut" />
<item type="id" name="copy" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 655deac..2e96c65 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3268,6 +3268,8 @@
<public name="accessibilityActionSwipeUp" />
<public name="accessibilityActionSwipeDown" />
<public name="accessibilityActionShowSuggestions" />
+ <public name="inputExtractAction" />
+ <public name="inputExtractAccessories" />
</staging-public-group>
<staging-public-group type="style" first-id="0x01dd0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 44ede49..49a12d1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3872,6 +3872,11 @@
<!-- Message of notification shown when serial console is enabled. [CHAR LIMIT=NONE] -->
<string name="console_running_notification_message">Performance is impacted. To disable, check bootloader.</string>
+ <!-- Title of notification shown when MTE status override is enabled. [CHAR LIMIT=NONE] -->
+ <string name="mte_override_notification_title">Experimental MTE enabled</string>
+ <!-- Message of notification shown when MTE status override is enabled. [CHAR LIMIT=NONE] -->
+ <string name="mte_override_notification_message">Performance and stability might be impacted. Reboot to disable. If enabled using arm64.memtag.bootctl, set it to "none" beforehand.</string>
+
<!-- Title of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
<string name="usb_contaminant_detected_title">Liquid or debris in USB port</string>
<!-- Message of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
@@ -5459,6 +5464,15 @@
<xliff:g id="app_name" example="Gmail">%1$s</xliff:g> is not available right now.
</string>
+ <!-- Title of the dialog shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_title"><xliff:g id="activity" example="Permission dialog">%1$s</xliff:g> unavailable</string>
+ <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your Android TV device instead.</string>
+ <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your tablet instead.</string>
+ <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your phone instead.</string>
+
<!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
<string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
<!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6cc93fc..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" />
@@ -2080,6 +2081,8 @@
<java-symbol type="string" name="test_harness_mode_notification_message" />
<java-symbol type="string" name="console_running_notification_title" />
<java-symbol type="string" name="console_running_notification_message" />
+ <java-symbol type="string" name="mte_override_notification_title" />
+ <java-symbol type="string" name="mte_override_notification_message" />
<java-symbol type="string" name="taking_remote_bugreport_notification_title" />
<java-symbol type="string" name="share_remote_bugreport_notification_title" />
<java-symbol type="string" name="sharing_remote_bugreport_notification_title" />
@@ -3264,6 +3267,9 @@
<java-symbol type="string" name="app_blocked_title" />
<java-symbol type="string" name="app_blocked_message" />
+ <java-symbol type="string" name="app_streaming_blocked_title" />
+ <java-symbol type="string" name="app_streaming_blocked_message" />
+
<!-- Used internally for assistant to launch activity transitions -->
<java-symbol type="id" name="cross_task_transition" />
@@ -3505,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"/>
@@ -3517,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" />
@@ -4667,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/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 75da0bf..1467fed 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -29,7 +29,6 @@
import android.os.IBinder;
import android.os.PersistableBundle;
import android.util.MergedConfiguration;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
@@ -110,7 +109,6 @@
private ProfilerInfo mProfilerInfo;
private IBinder mAssistToken;
private IBinder mShareableActivityToken;
- private FixedRotationAdjustments mFixedRotationAdjustments;
private boolean mLaunchedFromBubble;
LaunchActivityItemBuilder setIntent(Intent intent) {
@@ -203,11 +201,6 @@
return this;
}
- LaunchActivityItemBuilder setFixedRotationAdjustments(FixedRotationAdjustments fra) {
- mFixedRotationAdjustments = fra;
- return this;
- }
-
LaunchActivityItemBuilder setLaunchedFromBubble(boolean launchedFromBubble) {
mLaunchedFromBubble = launchedFromBubble;
return this;
@@ -218,8 +211,8 @@
mCurConfig, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor,
mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
- null /* activityClientController */, mFixedRotationAdjustments,
- mShareableActivityToken, mLaunchedFromBubble);
+ null /* activityClientController */, mShareableActivityToken,
+ mLaunchedFromBubble);
}
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 60d48b2..b2c4274 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -56,9 +56,6 @@
import android.os.RemoteException;
import android.os.SharedMemory;
import android.platform.test.annotations.Presubmit;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
-import android.view.DisplayCutout;
-import android.view.Surface;
import android.view.autofill.AutofillId;
import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationSpec;
@@ -198,8 +195,6 @@
bundle.putParcelable("data", new ParcelableData(1));
PersistableBundle persistableBundle = new PersistableBundle();
persistableBundle.putInt("k", 4);
- FixedRotationAdjustments fixedRotationAdjustments = new FixedRotationAdjustments(
- Surface.ROTATION_90, 1920, 1080, DisplayCutout.NO_CUTOUT);
LaunchActivityItem item = new LaunchActivityItemBuilder()
.setIntent(intent).setIdent(ident).setInfo(activityInfo).setCurConfig(config())
@@ -207,8 +202,7 @@
.setProcState(procState).setState(bundle).setPersistentState(persistableBundle)
.setPendingResults(resultInfoList()).setActivityOptions(ActivityOptions.makeBasic())
.setPendingNewIntents(referrerIntentList()).setIsForward(true)
- .setAssistToken(new Binder()).setFixedRotationAdjustments(fixedRotationAdjustments)
- .setShareableActivityToken(new Binder())
+ .setAssistToken(new Binder()).setShareableActivityToken(new Binder())
.build();
writeAndPrepareForReading(item);
@@ -359,23 +353,6 @@
assertTrue(transaction.equals(result));
}
- @Test
- public void testFixedRotationAdjustments() {
- ClientTransaction transaction = ClientTransaction.obtain(new StubAppThread(),
- null /* activityToken */);
- transaction.addCallback(FixedRotationAdjustmentsItem.obtain(new Binder(),
- new FixedRotationAdjustments(Surface.ROTATION_270, 1920, 1080,
- DisplayCutout.NO_CUTOUT)));
-
- writeAndPrepareForReading(transaction);
-
- // Read from parcel and assert
- ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
-
- assertEquals(transaction.hashCode(), result.hashCode());
- assertTrue(transaction.equals(result));
- }
-
/** Write to {@link #mParcel} and reset its position to prepare for reading from the start. */
private void writeAndPrepareForReading(Parcelable parcelable) {
parcelable.writeToParcel(mParcel, 0 /* flags */);
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 34a8bde..87c167c 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -281,37 +281,6 @@
}
@SmallTest
- public void testOverrideDisplayAdjustments() {
- final int originalOverrideDensity = 200;
- final int overrideDisplayDensity = 400;
- final Binder token = new Binder();
- final Configuration overrideConfig = new Configuration();
- overrideConfig.densityDpi = originalOverrideDensity;
- final Resources resources = mResourcesManager.createBaseTokenResources(
- token, APP_ONE_RES_DIR, null /* splitResDirs */, null /* legacyOverlayDirs */,
- null /* overlayDirs */,null /* libDirs */, Display.DEFAULT_DISPLAY, overrideConfig,
- CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null /* classLoader */,
- null /* loaders */);
-
- // Update the override.
- boolean handled = mResourcesManager.overrideTokenDisplayAdjustments(token,
- adjustments -> adjustments.getConfiguration().densityDpi = overrideDisplayDensity);
-
- assertTrue(handled);
- assertTrue(resources.hasOverrideDisplayAdjustments());
- assertEquals(overrideDisplayDensity,
- resources.getDisplayAdjustments().getConfiguration().densityDpi);
-
- // Clear the override.
- handled = mResourcesManager.overrideTokenDisplayAdjustments(token, null /* override */);
-
- assertTrue(handled);
- assertFalse(resources.hasOverrideDisplayAdjustments());
- assertEquals(originalOverrideDensity,
- resources.getDisplayAdjustments().getConfiguration().densityDpi);
- }
-
- @SmallTest
public void testChangingActivityDisplayDoesntOverrideDisplayRequestedByResources() {
Binder activity = new Binder();
diff --git a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
index 3cf1722..afbf8db 100644
--- a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
+++ b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
@@ -19,9 +19,6 @@
import static org.junit.Assert.assertEquals;
import android.content.res.Configuration;
-import android.graphics.Point;
-import android.util.DisplayMetrics;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -70,48 +67,4 @@
assertEquals(configuration, newAdjustments.getConfiguration());
}
-
- @Test
- public void testFixedRotationAdjustments() {
- final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
- final int realRotation = Surface.ROTATION_0;
- final int fixedRotation = Surface.ROTATION_90;
-
- final int appWidth = 1080;
- final int appHeight = 1920;
- mDisplayAdjustments.setFixedRotationAdjustments(new FixedRotationAdjustments(
- fixedRotation, appWidth, appHeight, null /* cutout */));
-
- final int w = 1000;
- final int h = 2000;
- final Point size = new Point(w, h);
- mDisplayAdjustments.adjustSize(size, realRotation);
-
- assertEquals(fixedRotation, mDisplayAdjustments.getRotation(realRotation));
- assertEquals(new Point(h, w), size);
-
- final DisplayMetrics metrics = new DisplayMetrics();
- metrics.xdpi = metrics.noncompatXdpi = w;
- metrics.widthPixels = metrics.noncompatWidthPixels = w;
- metrics.ydpi = metrics.noncompatYdpi = h;
- metrics.heightPixels = metrics.noncompatHeightPixels = h;
-
- final DisplayMetrics flippedMetrics = new DisplayMetrics();
- // The physical dpi should not be adjusted.
- flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = w;
- flippedMetrics.widthPixels = flippedMetrics.noncompatWidthPixels = h;
- flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = h;
- flippedMetrics.heightPixels = flippedMetrics.noncompatHeightPixels = w;
-
- mDisplayAdjustments.adjustMetrics(metrics, realRotation);
-
- assertEquals(flippedMetrics, metrics);
-
- mDisplayAdjustments.adjustGlobalAppMetrics(metrics);
-
- assertEquals(appWidth, metrics.widthPixels);
- assertEquals(appWidth, metrics.noncompatWidthPixels);
- assertEquals(appHeight, metrics.heightPixels);
- assertEquals(appHeight, metrics.noncompatHeightPixels);
- }
}
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index 74cdd21..10f6f1f 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -16,3 +16,6 @@
# Scroll Capture
per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
+
+# Stylus
+per-file stylus/* = file:/core/java/android/text/OWNERS
\ No newline at end of file
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/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 269d842..f8db069 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -297,8 +297,7 @@
null /* voiceInteractor */, null /* state */, null /* persistentState */,
null /* pendingResults */, null /* pendingNewIntents */,
null /* activityOptions */, true /* isForward */, null /* profilerInfo */,
- mThread /* client */, null /* asssitToken */,
- null /* fixedRotationAdjustments */, null /* shareableActivityToken */,
+ mThread /* client */, null /* asssitToken */, null /* shareableActivityToken */,
false /* launchedFromBubble */);
}
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index f8dd153..0c939ec 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -27,6 +27,7 @@
import static com.google.common.truth.Truth.assertThat;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
@@ -34,7 +35,6 @@
import android.hardware.display.DisplayManagerGlobal;
import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -49,8 +49,6 @@
import org.mockito.Mockito;
import org.mockito.quality.Strictness;
-import java.util.function.Consumer;
-
/**
* Tests for {@link Display}.
*
@@ -96,14 +94,12 @@
// Ensure no adjustments are set before each test.
mApplicationContext = ApplicationProvider.getApplicationContext();
- DisplayAdjustments displayAdjustments =
- mApplicationContext.getResources().getDisplayAdjustments();
- displayAdjustments.setFixedRotationAdjustments(null);
- mApplicationContext.getResources().overrideDisplayAdjustments(null);
mApplicationContext.getResources().getConfiguration().windowConfiguration.setAppBounds(
null);
mApplicationContext.getResources().getConfiguration().windowConfiguration.setMaxBounds(
null);
+ mApplicationContext.getResources().getConfiguration().windowConfiguration
+ .setDisplayRotation(WindowConfiguration.ROTATION_UNDEFINED);
mDisplayInfo.rotation = ROTATION_0;
mDisplayManagerGlobal = mock(DisplayManagerGlobal.class);
@@ -151,41 +147,11 @@
}
@Test
- public void testGetRotation_displayAdjustmentsWithoutOverride_rotationNotAdjusted() {
- // GIVEN display is not rotated.
- setDisplayInfoPortrait(mDisplayInfo);
- // GIVEN fixed rotation adjustments are rotated, but no override is set.
- DisplayAdjustments displayAdjustments = DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
- final FixedRotationAdjustments fixedRotationAdjustments =
- new FixedRotationAdjustments(ROTATION_90, APP_WIDTH, APP_HEIGHT,
- DisplayCutout.NO_CUTOUT);
- displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments);
- // GIVEN display is constructed with display adjustments.
- final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
- displayAdjustments);
- // THEN rotation is not adjusted since no override was set.
- assertThat(display.getRotation()).isEqualTo(ROTATION_0);
- }
-
- @Test
- public void testGetRotation_resourcesWithoutOverride_rotationNotAdjusted() {
- // GIVEN display is not rotated.
- setDisplayInfoPortrait(mDisplayInfo);
- // GIVEN fixed rotation adjustments are rotated, but no override is set.
- setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
- // GIVEN display is constructed with default resources.
- final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
- mApplicationContext.getResources());
- // THEN rotation is not adjusted since no override is set.
- assertThat(display.getRotation()).isEqualTo(ROTATION_0);
- }
-
- @Test
public void testGetRotation_resourcesWithOverrideDisplayAdjustments_rotationAdjusted() {
// GIVEN display is not rotated.
setDisplayInfoPortrait(mDisplayInfo);
// GIVEN fixed rotation adjustments are rotated, and an override is set.
- setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
+ setLocalDisplayInConfig(mApplicationContext.getResources(), ROTATION_90);
// GIVEN display is constructed with default resources.
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
mApplicationContext.getResources());
@@ -234,37 +200,11 @@
}
@Test
- public void testGetRealSize_resourcesPortraitWithFixedRotation_notRotatedLogicalSize() {
- // GIVEN display is rotated.
- setDisplayInfoLandscape(mDisplayInfo);
- // GIVEN fixed rotation adjustments are rotated.
- setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0);
- // GIVEN display is constructed with default resources.
- final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
- mApplicationContext.getResources());
- // THEN real size matches display orientation.
- verifyRealSizeIsLandscape(display);
- }
-
- @Test
- public void testGetRealSize_resourcesWithLandscapeFixedRotation_notRotatedLogicalSize() {
- // GIVEN display is not rotated.
- setDisplayInfoPortrait(mDisplayInfo);
- // GIVEN fixed rotation adjustments are rotated.
- setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
- // GIVEN display is constructed with default resources.
- final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
- mApplicationContext.getResources());
- // THEN real size matches display orientation.
- verifyRealSizeIsPortrait(display);
- }
-
- @Test
public void testGetRealSize_resourcesWithPortraitOverrideRotation_rotatedLogicalSize() {
// GIVEN display is rotated.
setDisplayInfoLandscape(mDisplayInfo);
// GIVEN fixed rotation adjustments are rotated, and an override is set.
- setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0);
+ setLocalDisplayInConfig(mApplicationContext.getResources(), ROTATION_0);
// GIVEN display is constructed with default resources.
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
mApplicationContext.getResources());
@@ -277,7 +217,7 @@
// GIVEN display is not rotated.
setDisplayInfoPortrait(mDisplayInfo);
// GIVEN fixed rotation adjustments are rotated, and an override is set.
- setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
+ setLocalDisplayInConfig(mApplicationContext.getResources(), ROTATION_90);
// GIVEN display is constructed with default resources.
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
mApplicationContext.getResources());
@@ -380,37 +320,11 @@
}
@Test
- public void testGetRealMetrics_resourcesPortraitWithFixedRotation_notRotatedLogicalSize() {
- // GIVEN display is rotated.
- setDisplayInfoLandscape(mDisplayInfo);
- // GIVEN fixed rotation adjustments are rotated.
- setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0);
- // GIVEN display is constructed with default resources.
- final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
- mApplicationContext.getResources());
- // THEN real metrics matches display orientation.
- verifyRealMetricsIsLandscape(display);
- }
-
- @Test
- public void testGetRealMetrics_resourcesWithLandscapeFixedRotation_notRotatedLogicalSize() {
- // GIVEN display is not rotated.
- setDisplayInfoPortrait(mDisplayInfo);
- // GIVEN fixed rotation adjustments are rotated.
- setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
- // GIVEN display is constructed with default resources.
- final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
- mApplicationContext.getResources());
- // THEN real metrics matches display orientation.
- verifyRealMetricsIsPortrait(display);
- }
-
- @Test
public void testGetRealMetrics_resourcesWithPortraitOverrideRotation_rotatedLogicalSize() {
// GIVEN display is rotated.
setDisplayInfoLandscape(mDisplayInfo);
// GIVEN fixed rotation adjustments are rotated with an override.
- setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0);
+ setLocalDisplayInConfig(mApplicationContext.getResources(), ROTATION_0);
// GIVEN display is constructed with default resources.
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
mApplicationContext.getResources());
@@ -423,7 +337,7 @@
// GIVEN display is not rotated.
setDisplayInfoPortrait(mDisplayInfo);
// GIVEN fixed rotation adjustments are rotated.
- setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90);
+ setLocalDisplayInConfig(mApplicationContext.getResources(), ROTATION_90);
// GIVEN display is constructed with default resources.
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
mApplicationContext.getResources());
@@ -569,27 +483,8 @@
assertThat(metrics.heightPixels).isEqualTo(bounds.height());
}
- private static FixedRotationAdjustments setOverrideFixedRotationAdjustments(
- Resources resources, @Surface.Rotation int rotation) {
- FixedRotationAdjustments fixedRotationAdjustments =
- setFixedRotationAdjustments(resources, rotation);
- resources.overrideDisplayAdjustments(
- buildOverrideRotationAdjustments(fixedRotationAdjustments));
- return fixedRotationAdjustments;
- }
-
- private static FixedRotationAdjustments setFixedRotationAdjustments(Resources resources,
+ private static void setLocalDisplayInConfig(Resources resources,
@Surface.Rotation int rotation) {
- final FixedRotationAdjustments fixedRotationAdjustments =
- new FixedRotationAdjustments(rotation, APP_WIDTH, APP_HEIGHT,
- DisplayCutout.NO_CUTOUT);
- resources.getDisplayAdjustments().setFixedRotationAdjustments(fixedRotationAdjustments);
- return fixedRotationAdjustments;
- }
-
- private static Consumer<DisplayAdjustments> buildOverrideRotationAdjustments(
- FixedRotationAdjustments fixedRotationAdjustments) {
- return consumedDisplayAdjustments
- -> consumedDisplayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments);
+ resources.getConfiguration().windowConfiguration.setDisplayRotation(rotation);
}
}
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/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 8054fd4..f4e965f 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -125,8 +125,8 @@
boolean isAdasGnssLocationEnabledForUser(int userId);
void setAdasGnssLocationEnabledForUser(boolean enabled, int userId);
- boolean isAutoGnssSuspended();
- void setAutoGnssSuspended(boolean suspended);
+ boolean isAutomotiveGnssSuspended();
+ void setAutomotiveGnssSuspended(boolean suspended);
void addTestProvider(String name, in ProviderProperties properties,
in List<String> locationTags, String packageName, @nullable String attributionTag);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 9109a18..d275628 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -758,45 +758,41 @@
}
/**
- * Set whether GNSS requests are suspended on the device.
+ * Set whether GNSS requests are suspended on the automotive device.
*
- * This method was added to help support power management use cases on automotive devices. More
- * specifically, it is being added to fix a suspend to RAM issue where the SoC can't go into
- * a lower power state when applications are actively requesting GNSS updates.
+ * For devices where GNSS prevents the system from going into a low power state, GNSS should
+ * be suspended right before going into the lower power state and resumed right after the device
+ * wakes up.
*
- * Ideally, the issue should be fixed at a lower layer in the stack, but this API introduces a
- * workaround in the platform layer. This API allows car specific services to halt GNSS requests
- * based on changes to the car power policy, which will in turn enable the device to go into
- * suspend.
+ * This method disables GNSS and should only be used for power management use cases such as
+ * suspend-to-RAM or suspend-to-disk.
*
* @hide
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS)
- public void setAutoGnssSuspended(boolean suspended) {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
+ public void setAutomotiveGnssSuspended(boolean suspended) {
try {
- mService.setAutoGnssSuspended(suspended);
+ mService.setAutomotiveGnssSuspended(suspended);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Return whether GNSS requests are suspended or not.
- *
- * This method was added to help support power management use cases on automotive devices. More
- * specifically, it is being added as part of the fix for a suspend to RAM issue where the SoC
- * can't go into a lower power state when applications are actively requesting GNSS updates.
+ * Return whether GNSS requests are suspended on the automotive device.
*
* @return true if GNSS requests are suspended and false if they aren't.
*
* @hide
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS)
- public boolean isAutoGnssSuspended() {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
+ public boolean isAutomotiveGnssSuspended() {
try {
- return mService.isAutoGnssSuspended();
+ return mService.isAutomotiveGnssSuspended();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 41666c7..75236f4 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -1851,6 +1851,7 @@
*
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
public int getClientPid(@NonNull String sessionId) {
return getClientPidInternal(sessionId);
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index ffc4433..1e32cad 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -68,6 +68,7 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -1602,25 +1603,27 @@
*
* @param statusTypes an array of status types.
*
- * @return an array of current readiness states. {@code null} if the operation failed or
- * unsupported versions.
+ * @return a list of current readiness states. It is empty if the operation fails or unsupported
+ * versions.
* @throws IllegalStateException if there is no active frontend currently.
*/
- @Nullable
- @SuppressLint("ArrayReturn")
- @SuppressWarnings("NullableCollection")
- public FrontendStatusReadiness[] getFrontendStatusReadiness(
+ @NonNull
+ public List<FrontendStatusReadiness> getFrontendStatusReadiness(
@NonNull @FrontendStatusType int[] statusTypes) {
mFrontendLock.lock();
try {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
- TunerVersionChecker.TUNER_VERSION_2_0, "Remove output PID")) {
- return null;
+ TunerVersionChecker.TUNER_VERSION_2_0, "Get fronted status readiness")) {
+ return Collections.EMPTY_LIST;
}
if (mFrontend == null) {
throw new IllegalStateException("frontend is not initialized");
}
- return nativeGetFrontendStatusReadiness(statusTypes);
+ FrontendStatusReadiness[] readiness = nativeGetFrontendStatusReadiness(statusTypes);
+ if (readiness == null) {
+ return Collections.EMPTY_LIST;
+ }
+ return Arrays.asList(readiness);
} finally {
mFrontendLock.unlock();
}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java b/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java
index 52527b3..c30753c 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java
@@ -30,7 +30,7 @@
* @hide
*/
@SystemApi
-public class FrontendStatusReadiness {
+public final class FrontendStatusReadiness {
/** @hide */
@IntDef({FRONTEND_STATUS_READINESS_UNDEFINED, FRONTEND_STATUS_READINESS_UNAVAILABLE,
FRONTEND_STATUS_READINESS_UNSTABLE, FRONTEND_STATUS_READINESS_STABLE,
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index feae606..18b779f 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -87,6 +87,7 @@
"android.hardware.drm@1.4",
"android.hidl.memory@1.0",
"android.hidl.token@1.0-utils",
+ "android.hardware.drm-V1-ndk",
],
header_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/omapi/OWNERS b/omapi/OWNERS
new file mode 100644
index 0000000..5682fd3
--- /dev/null
+++ b/omapi/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 456592
+
+zachoverflow@google.com
+alisher@google.com
+jackcwyu@google.com
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
index 73b9c72..56faa52 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
@@ -30,7 +30,6 @@
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.service.NetworkIdentityProto;
-import android.telephony.Annotation;
import android.telephony.TelephonyManager;
import android.util.proto.ProtoOutputStream;
@@ -275,8 +274,7 @@
@Deprecated
@NonNull
public static NetworkIdentity buildNetworkIdentity(Context context,
- @NonNull NetworkStateSnapshot snapshot,
- boolean defaultNetwork, @Annotation.NetworkType int ratType) {
+ @NonNull NetworkStateSnapshot snapshot, boolean defaultNetwork, int ratType) {
final NetworkIdentity.Builder builder = new NetworkIdentity.Builder()
.setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork);
if (snapshot.getLegacyType() == TYPE_MOBILE && ratType != NETWORK_TYPE_ALL) {
@@ -433,7 +431,7 @@
* @return this builder.
*/
@NonNull
- public Builder setRatType(@Annotation.NetworkType int ratType) {
+ public Builder setRatType(int ratType) {
if (!CollectionUtils.contains(TelephonyManager.getAllNetworkTypes(), ratType)
&& ratType != TelephonyManager.NETWORK_TYPE_UNKNOWN
&& ratType != NetworkStatsManager.NETWORK_TYPE_5G_NSA) {
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
index 27e717f..9b58b01 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
@@ -47,7 +47,6 @@
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Annotation.NetworkType;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -203,7 +202,7 @@
* @hide
*/
public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId,
- @NetworkType int ratType, int metered) {
+ int ratType, int metered) {
if (TextUtils.isEmpty(subscriberId)) {
return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null /* subscriberId */,
null /* matchSubscriberIds */,
@@ -1039,7 +1038,7 @@
* @return this builder.
*/
@NonNull
- public Builder setRatType(@NetworkType int ratType) {
+ public Builder setRatType(int ratType) {
// Input will be validated with the match rule when building the template.
mRatType = ratType;
return this;
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/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
index 3e35e603..5bba0b1 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -24,7 +24,6 @@
import android.annotation.NonNull;
import android.content.Context;
-import android.telephony.Annotation;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyDisplayInfo;
@@ -59,8 +58,7 @@
* @param collapsedRatType collapsed RAT type.
* @see android.app.usage.NetworkStatsManager#getCollapsedRatType(int).
*/
- void onCollapsedRatTypeChanged(@NonNull String subscriberId,
- @Annotation.NetworkType int collapsedRatType);
+ void onCollapsedRatTypeChanged(@NonNull String subscriberId, int collapsedRatType);
}
private final Delegate mDelegate;
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/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index e51bb45..3eb6ea9 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -42,6 +42,7 @@
@VisibleForTesting
View.OnClickListener mLearnMoreListener;
private CharSequence mContentDescription;
+ private CharSequence mLearnMoreText;
private CharSequence mLearnMoreContentDescription;
private FooterLearnMoreSpan mLearnMoreSpan;
@@ -69,7 +70,12 @@
TextView learnMore = holder.itemView.findViewById(R.id.settingslib_learn_more);
if (learnMore != null && mLearnMoreListener != null) {
learnMore.setVisibility(View.VISIBLE);
- SpannableString learnMoreText = new SpannableString(learnMore.getText());
+ if (TextUtils.isEmpty(mLearnMoreText)) {
+ mLearnMoreText = learnMore.getText();
+ } else {
+ learnMore.setText(mLearnMoreText);
+ }
+ SpannableString learnMoreText = new SpannableString(mLearnMoreText);
if (mLearnMoreSpan != null) {
learnMoreText.removeSpan(mLearnMoreSpan);
}
@@ -123,6 +129,18 @@
}
/**
+ * Sets the learn more text.
+ *
+ * @param learnMoreText The string of the learn more text.
+ */
+ public void setLearnMoreText(CharSequence learnMoreText) {
+ if (!TextUtils.equals(mLearnMoreText, learnMoreText)) {
+ mLearnMoreText = learnMoreText;
+ notifyChanged();
+ }
+ }
+
+ /**
* To set content description of the learn more text. This can use for talkback
* environment if developer wants to have a customization content.
*
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/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java
rename to packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
index bec5fc8..afd3626 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.settingslib.devicestate;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
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/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 10ac829..3b18c57 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -64,6 +64,20 @@
}
@Test
+ public void setLearnMoreText_shouldSetAsTextInLearnMore() {
+ final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(
+ LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null));
+ mFooterPreference.setLearnMoreText("Custom learn more");
+ mFooterPreference.setLearnMoreAction(view -> { /* do nothing */ } /* listener */);
+
+ mFooterPreference.onBindViewHolder(holder);
+
+ assertThat(((TextView) holder.findViewById(
+ R.id.settingslib_learn_more)).getText().toString())
+ .isEqualTo("Custom learn more");
+ }
+
+ @Test
public void setContentDescription_contentSet_shouldGetSameContentDescription() {
mFooterPreference.setContentDescription("test");
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/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 68c8c3e..ffee894 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -20,6 +20,7 @@
import android.app.smartspace.SmartspaceAction;
import android.app.smartspace.SmartspaceTarget;
import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.uitemplatedata.SmartspaceTapAction;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -143,6 +144,18 @@
}
}
+ default void startFromAction(SmartspaceTapAction action, View v, boolean showOnLockscreen) {
+ try {
+ if (action.getIntent() != null) {
+ startIntent(v, action.getIntent(), showOnLockscreen);
+ } else if (action.getPendingIntent() != null) {
+ startPendingIntent(action.getPendingIntent(), showOnLockscreen);
+ }
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Could not launch intent for action: " + action, e);
+ }
+ }
+
/** Start the intent */
void startIntent(View v, Intent i, boolean showOnLockscreen);
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/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
index 214b284..43cd764 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt
@@ -16,6 +16,7 @@
package com.android.keyguard
+import android.app.StatusBarManager.SESSION_KEYGUARD
import android.content.Context
import android.hardware.biometrics.BiometricSourceType
import com.android.internal.annotations.VisibleForTesting
@@ -28,7 +29,7 @@
import com.android.keyguard.KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.SessionTracker
import java.io.FileDescriptor
import java.io.PrintWriter
import javax.inject.Inject
@@ -44,7 +45,7 @@
context: Context?,
private val uiEventLogger: UiEventLogger,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val dumpManager: DumpManager
+ private val sessionTracker: SessionTracker
) : CoreStartable(context) {
private var fingerprintLockedOut = false
private var faceLockedOut = false
@@ -53,7 +54,6 @@
private var timeout = false
override fun start() {
- dumpManager.registerDumpable(this)
mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged(
KeyguardUpdateMonitor.getCurrentUser())
keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback)
@@ -65,22 +65,17 @@
if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
val lockedOut = keyguardUpdateMonitor.isFingerprintLockedOut
if (lockedOut && !fingerprintLockedOut) {
- uiEventLogger.log(
- PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
} else if (!lockedOut && fingerprintLockedOut) {
- uiEventLogger.log(
- PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
}
fingerprintLockedOut = lockedOut
} else if (biometricSourceType == BiometricSourceType.FACE) {
val lockedOut = keyguardUpdateMonitor.isFaceLockedOut
if (lockedOut && !faceLockedOut) {
- uiEventLogger.log(
- PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
} else if (!lockedOut && faceLockedOut) {
- uiEventLogger.log(
- PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
}
faceLockedOut = lockedOut
}
@@ -95,20 +90,19 @@
val newEncryptedOrLockdown = keyguardUpdateMonitor.isEncryptedOrLockdown(userId)
if (newEncryptedOrLockdown && !encryptedOrLockdown) {
- uiEventLogger.log(
- PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
}
encryptedOrLockdown = newEncryptedOrLockdown
val newUnattendedUpdate = isUnattendedUpdate(strongAuthFlags)
if (newUnattendedUpdate && !unattendedUpdate) {
- uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
}
unattendedUpdate = newUnattendedUpdate
val newTimeout = isStrongAuthTimeout(strongAuthFlags)
if (newTimeout && !timeout) {
- uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT)
+ log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT)
}
timeout = newTimeout
}
@@ -123,6 +117,9 @@
) = containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) ||
containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT)
+ private fun log(event: PrimaryAuthRequiredEvent) =
+ uiEventLogger.log(event, sessionTracker.getSessionId(SESSION_KEYGUARD))
+
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
pw.println(" mFingerprintLockedOut=$fingerprintLockedOut")
pw.println(" mFaceLockedOut=$faceLockedOut")
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 49a8022..57997d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
+
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
@@ -36,7 +38,10 @@
import android.util.Slog;
import android.view.MotionEvent;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
@@ -53,6 +58,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -86,6 +92,7 @@
private final UserSwitcherController mUserSwitcherController;
private final GlobalSettings mGlobalSettings;
private final FeatureFlags mFeatureFlags;
+ private final SessionTracker mSessionTracker;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -191,7 +198,7 @@
mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
.setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
- : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE);
+ : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId());
}
public void reset() {
@@ -242,7 +249,8 @@
FalsingManager falsingManager,
UserSwitcherController userSwitcherController,
FeatureFlags featureFlags,
- GlobalSettings globalSettings) {
+ GlobalSettings globalSettings,
+ SessionTracker sessionTracker) {
super(view);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -261,6 +269,7 @@
mUserSwitcherController = userSwitcherController;
mFeatureFlags = featureFlags;
mGlobalSettings = globalSettings;
+ mSessionTracker = sessionTracker;
}
@Override
@@ -456,7 +465,7 @@
.setType(MetricsProto.MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype));
}
if (uiEvent != BouncerUiEvent.UNKNOWN) {
- mUiEventLogger.log(uiEvent);
+ mUiEventLogger.log(uiEvent, getSessionId());
}
if (finish) {
mSecurityCallback.finish(strongAuth, targetUserId);
@@ -599,6 +608,10 @@
}
}
+ private @Nullable InstanceId getSessionId() {
+ return mSessionTracker.getSessionId(SESSION_KEYGUARD);
+ }
+
/** Update keyguard position based on a tapped X coordinate. */
public void updateKeyguardPosition(float x) {
mView.updatePositionByTouchX(x);
@@ -622,6 +635,7 @@
private final GlobalSettings mGlobalSettings;
private final FeatureFlags mFeatureFlags;
private final UserSwitcherController mUserSwitcherController;
+ private final SessionTracker mSessionTracker;
@Inject
Factory(KeyguardSecurityContainer view,
@@ -639,7 +653,8 @@
FalsingManager falsingManager,
UserSwitcherController userSwitcherController,
FeatureFlags featureFlags,
- GlobalSettings globalSettings) {
+ GlobalSettings globalSettings,
+ SessionTracker sessionTracker) {
mView = view;
mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
mLockPatternUtils = lockPatternUtils;
@@ -655,6 +670,7 @@
mFeatureFlags = featureFlags;
mGlobalSettings = globalSettings;
mUserSwitcherController = userSwitcherController;
+ mSessionTracker = sessionTracker;
}
public KeyguardSecurityContainerController create(
@@ -664,7 +680,7 @@
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
mConfigurationController, mFalsingCollector, mFalsingManager,
- mUserSwitcherController, mFeatureFlags, mGlobalSettings);
+ mUserSwitcherController, mFeatureFlags, mGlobalSettings, mSessionTracker);
}
}
}
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/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 0b967b7..f00615b 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -23,6 +23,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.util.FloatProperty;
import android.util.Log;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
@@ -69,6 +70,19 @@
// 2f: maximum brightness is stretching a 1U to 3U, or a 4U to 6U
private static final float STRETCH_INTERVAL = 2f;
+ private static final FloatProperty<ViewScaler> VIEW_SCALER_HEIGHT_PROPERTY =
+ new FloatProperty<ViewScaler>("ViewScalerHeight") {
+ @Override
+ public void setValue(ViewScaler object, float value) {
+ object.setHeight(value);
+ }
+
+ @Override
+ public Float get(ViewScaler object) {
+ return object.getHeight();
+ }
+ };
+
@SuppressWarnings("unused")
private Context mContext;
@@ -147,6 +161,7 @@
public void setView(ExpandableView v) {
mView = v;
}
+
public void setHeight(float h) {
if (DEBUG_SCALE) Log.v(TAG, "SetHeight: setting to " + h);
mView.setActualHeight((int) h);
@@ -176,7 +191,7 @@
mCallback = callback;
mScaler = new ViewScaler();
mGravity = Gravity.TOP;
- mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
+ mScaleAnimation = ObjectAnimator.ofFloat(mScaler, VIEW_SCALER_HEIGHT_PROPERTY, 0f);
mPullGestureMinXSpan = mContext.getResources().getDimension(R.dimen.pull_span_min);
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
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/navigationbar/buttons/DeadZone.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
index 7e5b554..7fb58f0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
@@ -20,6 +20,7 @@
import android.content.res.Resources;
import android.graphics.Canvas;
import android.os.SystemClock;
+import android.util.FloatProperty;
import android.util.Slog;
import android.view.MotionEvent;
import android.view.Surface;
@@ -44,6 +45,20 @@
public static final int VERTICAL = 1; // Consume taps along the left edge.
private static final boolean CHATTY = true; // print to logcat when we eat a click
+
+ private static final FloatProperty<DeadZone> FLASH_PROPERTY =
+ new FloatProperty<DeadZone>("DeadZoneFlash") {
+ @Override
+ public void setValue(DeadZone object, float value) {
+ object.setFlash(value);
+ }
+
+ @Override
+ public Float get(DeadZone object) {
+ return object.getFlash();
+ }
+ };
+
private final NavigationBarController mNavBarController;
private final NavigationBarView mNavigationBarView;
@@ -63,7 +78,7 @@
private final Runnable mDebugFlash = new Runnable() {
@Override
public void run() {
- ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start();
+ ObjectAnimator.ofFloat(DeadZone.this, FLASH_PROPERTY, 1f, 0f).setDuration(150).start();
}
};
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 120c722..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
@@ -359,6 +359,13 @@
private void buildList() {
Trace.beginSection("ShadeListBuilder.buildList");
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
+
+ if (!mNotifStabilityManager.isPipelineRunAllowed()) {
+ mLogger.logPipelineRunSuppressed();
+ Trace.endSection();
+ return;
+ }
+
mPipelineState.setState(STATE_BUILD_STARTED);
// Step 1: Reset notification states
@@ -667,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);
@@ -676,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 {
@@ -819,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/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 327876c..fcc2b26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifPanelEventSource;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -56,17 +57,23 @@
*/
// TODO(b/204468557): Move to @CoordinatorScope
@SysUISingleton
-public class VisualStabilityCoordinator implements Coordinator, Dumpable {
+public class VisualStabilityCoordinator implements Coordinator, Dumpable,
+ NotifPanelEventSource.Callbacks {
private final DelayableExecutor mDelayableExecutor;
- private final WakefulnessLifecycle mWakefulnessLifecycle;
- private final StatusBarStateController mStatusBarStateController;
private final HeadsUpManager mHeadsUpManager;
+ private final NotifPanelEventSource mNotifPanelEventSource;
+ private final StatusBarStateController mStatusBarStateController;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mScreenOn;
private boolean mPanelExpanded;
private boolean mPulsing;
+ private boolean mNotifPanelCollapsing;
+ private boolean mNotifPanelLaunchingActivity;
+ private boolean mPipelineRunAllowed;
private boolean mReorderingAllowed;
+ private boolean mIsSuppressingPipelineRun = false;
private boolean mIsSuppressingGroupChange = false;
private final Set<String> mEntriesWithSuppressedSectionChange = new HashSet<>();
private boolean mIsSuppressingEntryReorder = false;
@@ -81,16 +88,17 @@
@Inject
public VisualStabilityCoordinator(
+ DelayableExecutor delayableExecutor,
DumpManager dumpManager,
HeadsUpManager headsUpManager,
- WakefulnessLifecycle wakefulnessLifecycle,
+ NotifPanelEventSource notifPanelEventSource,
StatusBarStateController statusBarStateController,
- DelayableExecutor delayableExecutor
- ) {
+ WakefulnessLifecycle wakefulnessLifecycle) {
mHeadsUpManager = headsUpManager;
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
mDelayableExecutor = delayableExecutor;
+ mNotifPanelEventSource = notifPanelEventSource;
dumpManager.registerDumpable(this);
}
@@ -103,6 +111,7 @@
mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
mPulsing = mStatusBarStateController.isPulsing();
+ mNotifPanelEventSource.registerCallbacks(this);
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
@@ -112,12 +121,19 @@
new NotifStabilityManager("VisualStabilityCoordinator") {
@Override
public void onBeginRun() {
+ mIsSuppressingPipelineRun = false;
mIsSuppressingGroupChange = false;
mEntriesWithSuppressedSectionChange.clear();
mIsSuppressingEntryReorder = false;
}
@Override
+ public boolean isPipelineRunAllowed() {
+ mIsSuppressingPipelineRun |= !mPipelineRunAllowed;
+ return mPipelineRunAllowed;
+ }
+
+ @Override
public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
final boolean isGroupChangeAllowedForEntry =
mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey());
@@ -154,9 +170,12 @@
};
private void updateAllowedStates() {
+ mPipelineRunAllowed = !isPanelCollapsingOrLaunchingActivity();
mReorderingAllowed = isReorderingAllowed();
- if (mReorderingAllowed && (mIsSuppressingGroupChange || isSuppressingSectionChange()
- || mIsSuppressingEntryReorder)) {
+ if ((mPipelineRunAllowed && mIsSuppressingPipelineRun)
+ || (mReorderingAllowed && (mIsSuppressingGroupChange
+ || isSuppressingSectionChange()
+ || mIsSuppressingEntryReorder))) {
mNotifStabilityManager.invalidateList();
}
}
@@ -165,6 +184,10 @@
return !mEntriesWithSuppressedSectionChange.isEmpty();
}
+ private boolean isPanelCollapsingOrLaunchingActivity() {
+ return mNotifPanelCollapsing || mNotifPanelLaunchingActivity;
+ }
+
private boolean isReorderingAllowed() {
return (!mScreenOn || !mPanelExpanded) && !mPulsing;
}
@@ -248,4 +271,16 @@
pw.println(" " + key);
}
}
+
+ @Override
+ public void onPanelCollapsingChanged(boolean isCollapsing) {
+ mNotifPanelCollapsing = isCollapsing;
+ updateAllowedStates();
+ }
+
+ @Override
+ public void onLaunchingActivityChanged(boolean isLaunchingActivity) {
+ mNotifPanelLaunchingActivity = isLaunchingActivity;
+ updateAllowedStates();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index ba3e855..f8bf85f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -306,6 +306,9 @@
}
}
}
+
+ fun logPipelineRunSuppressed() =
+ buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." }
}
private const val TAG = "ShadeListBuilder"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
index 60f557c..4464498 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt
@@ -27,6 +27,16 @@
*/
abstract class NotifStabilityManager protected constructor(name: String) :
Pluggable<NotifStabilityManager>(name) {
+
+ /**
+ * Called prior to running the pipeline to suppress any visual changes. Ex: collapse animation
+ * is playing, moving stuff around simultaneously will look janky.
+ *
+ * Note: this is invoked *before* [onBeginRun], so that implementors can reference state
+ * maintained from a previous run.
+ */
+ abstract fun isPipelineRunAllowed(): Boolean
+
/**
* Called at the beginning of every pipeline run to perform any necessary cleanup from the
* previous run.
@@ -76,6 +86,7 @@
/** The default, no-op instance of the stability manager which always allows all changes */
object DefaultNotifStabilityManager : NotifStabilityManager("DefaultNotifStabilityManager") {
+ override fun isPipelineRunAllowed(): Boolean = true
override fun onBeginRun() {}
override fun isGroupChangeAllowed(entry: NotificationEntry): Boolean = true
override fun isSectionChangeAllowed(entry: NotificationEntry): Boolean = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifPanelEventSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifPanelEventSource.kt
new file mode 100644
index 0000000..920d3c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifPanelEventSource.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.render
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.NotificationPanelViewController
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope
+import com.android.systemui.util.ListenerSet
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+
+/** Provides certain notification panel events. */
+interface NotifPanelEventSource {
+
+ /** Registers callbacks to be invoked when notification panel events occur. */
+ fun registerCallbacks(callbacks: Callbacks)
+
+ /** Unregisters callbacks previously registered via [.registerCallbacks] */
+ fun unregisterCallbacks(callbacks: Callbacks)
+
+ /** Callbacks for certain notification panel events. */
+ interface Callbacks {
+
+ /** Invoked when the notification panel starts or stops collapsing. */
+ fun onPanelCollapsingChanged(isCollapsing: Boolean)
+
+ /**
+ * Invoked when the notification panel starts or stops launching an [android.app.Activity].
+ */
+ fun onLaunchingActivityChanged(isLaunchingActivity: Boolean)
+ }
+}
+
+@Module
+abstract class NotifPanelEventSourceModule {
+
+ @Binds
+ @SysUISingleton
+ abstract fun bindEventSource(manager: NotifPanelEventSourceManager): NotifPanelEventSource
+
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ fun provideManager(): NotifPanelEventSourceManager = NotifPanelEventSourceManagerImpl()
+ }
+}
+
+@Module
+object StatusBarNotifPanelEventSourceModule {
+ @JvmStatic
+ @Provides
+ @IntoSet
+ @StatusBarScope
+ fun bindStartable(
+ manager: NotifPanelEventSourceManager,
+ notifPanelController: NotificationPanelViewController
+ ): StatusBarComponent.Startable =
+ EventSourceStatusBarStartableImpl(manager, notifPanelController)
+}
+
+/**
+ * Management layer that bridges [SysUiSingleton] and [StatusBarScope]. Necessary because code that
+ * wants to listen to [NotifPanelEventSource] lives in [SysUiSingleton], but the events themselves
+ * come from [NotificationPanelViewController] in [StatusBarScope].
+ */
+interface NotifPanelEventSourceManager : NotifPanelEventSource {
+ var eventSource: NotifPanelEventSource?
+}
+
+private class NotifPanelEventSourceManagerImpl
+ : NotifPanelEventSourceManager, NotifPanelEventSource.Callbacks {
+
+ private val callbackSet = ListenerSet<NotifPanelEventSource.Callbacks>()
+
+ override var eventSource: NotifPanelEventSource? = null
+ set(value) {
+ field?.unregisterCallbacks(this)
+ value?.registerCallbacks(this)
+ field = value
+ }
+
+ override fun registerCallbacks(callbacks: NotifPanelEventSource.Callbacks) {
+ callbackSet.addIfAbsent(callbacks)
+ }
+
+ override fun unregisterCallbacks(callbacks: NotifPanelEventSource.Callbacks) {
+ callbackSet.remove(callbacks)
+ }
+
+ override fun onPanelCollapsingChanged(isCollapsing: Boolean) {
+ callbackSet.forEach { it.onPanelCollapsingChanged(isCollapsing) }
+ }
+
+ override fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {
+ callbackSet.forEach { it.onLaunchingActivityChanged(isLaunchingActivity) }
+ }
+}
+
+private class EventSourceStatusBarStartableImpl(
+ private val manager: NotifPanelEventSourceManager,
+ private val notifPanelController: NotificationPanelViewController
+) : StatusBarComponent.Startable {
+
+ override fun start() {
+ manager.eventSource = notifPanelController
+ }
+
+ override fun stop() {
+ manager.eventSource = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 05c40b2..45a9092 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -67,6 +67,7 @@
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifPanelEventSourceModule;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -101,8 +102,9 @@
* Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
*/
@Module(includes = {
+ CoordinatorsModule.class,
+ NotifPanelEventSourceModule.class,
NotificationSectionHeadersModule.class,
- CoordinatorsModule.class
})
public interface NotificationsModule {
@Binds
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/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 7dc2e19..ce3e27c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -565,6 +565,10 @@
}
}
+ public float getDozeAmount() {
+ return mDozeAmount;
+ }
+
/**
* Is the device fully awake, which is different from not tark at all when there are pulsing
* notifications.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
deleted file mode 100644
index bd5b7d7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.stack;
-
-import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
-import android.util.AttributeSet;
-
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-
-/**
- * Root view to insert Lock screen media controls into the notification stack.
- */
-public class MediaContainerView extends ExpandableView {
-
- public MediaContainerView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public long performRemoveAnimation(long duration, long delay, float translationDirection,
- boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
- return 0;
- }
-
- @Override
- public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
- Runnable onEnd) {
- // No animation, it doesn't need it, this would be local
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
new file mode 100644
index 0000000..b8f28b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.stack
+
+import android.animation.AnimatorListenerAdapter
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Canvas
+import android.graphics.Path
+import android.graphics.RectF
+import android.util.AttributeSet
+import com.android.systemui.R
+import com.android.systemui.statusbar.notification.row.ExpandableView
+
+/**
+ * Root view to insert Lock screen media controls into the notification stack.
+ */
+class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) {
+
+ var cornerRadius = 0f
+ var clipHeight = 0
+ var clipRect = RectF()
+ var clipPath = Path()
+
+ init {
+ setWillNotDraw(false) // Run onDraw after invalidate.
+ updateResources()
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration?) {
+ super.onConfigurationChanged(newConfig)
+ updateResources()
+ }
+
+ private fun updateResources() {
+ cornerRadius = context.resources
+ .getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat()
+ }
+
+ public override fun updateClipping() {
+ if (clipHeight != actualHeight) {
+ clipHeight = actualHeight
+ }
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+
+ val bounds = canvas.clipBounds
+ bounds.bottom = clipHeight
+ clipRect.set(bounds)
+
+ clipPath.reset()
+ clipPath.addRoundRect(clipRect, cornerRadius, cornerRadius, Path.Direction.CW)
+ canvas.clipPath(clipPath)
+ }
+
+
+ override fun performRemoveAnimation(duration: Long, delay: Long, translationDirection: Float,
+ isHeadsUpAnimation: Boolean, endLocation: Float,
+ onFinishedRunnable: Runnable?,
+ animationListener: AnimatorListenerAdapter?): Long {
+ return 0
+ }
+
+ override fun performAddAnimation(delay: Long, duration: Long, isHeadsUpAppear: Boolean,
+ onEnd: Runnable?) {
+ // No animation, it doesn't need it, this would be local
+ }
+}
\ No newline at end of file
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 eff8af0..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);
@@ -1273,8 +1276,7 @@
* @param listenerNeedsAnimation does the listener need to animate?
*/
private void updateStackPosition(boolean listenerNeedsAnimation) {
- // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD
- float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
+ final float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
+ mAmbientState.getOverExpansion()
- getCurrentOverScrollAmount(false /* top */);
final float fraction = mAmbientState.getExpansionFraction();
@@ -1284,15 +1286,31 @@
mOnStackYChanged.accept(listenerNeedsAnimation);
}
if (mQsExpansionFraction <= 0) {
- final float stackEndHeight = Math.max(0f,
- getHeight() - getEmptyBottomMargin() - mTopPadding);
- mAmbientState.setStackEndHeight(stackEndHeight);
- mAmbientState.setStackHeight(
- MathUtils.lerp(stackEndHeight * StackScrollAlgorithm.START_FRACTION,
- stackEndHeight, fraction));
+ final float endHeight = updateStackEndHeight(
+ getHeight(), getEmptyBottomMargin(), mTopPadding);
+ updateStackHeight(endHeight, fraction);
}
}
+ public float updateStackEndHeight(float height, float bottomMargin, float topPadding) {
+ final float stackEndHeight = Math.max(0f, height - bottomMargin - topPadding);
+ mAmbientState.setStackEndHeight(stackEndHeight);
+ return stackEndHeight;
+ }
+
+ public void updateStackHeight(float endHeight, float fraction) {
+ // During the (AOD<=>LS) transition where dozeAmount is changing,
+ // apply dozeAmount to stack height instead of expansionFraction
+ // to unfurl notifications on AOD=>LS wakeup (and furl up on LS=>AOD sleep)
+ final float dozeAmount = mAmbientState.getDozeAmount();
+ if (0f < dozeAmount && dozeAmount < 1f) {
+ fraction = 1f - dozeAmount;
+ }
+ mAmbientState.setStackHeight(
+ MathUtils.lerp(endHeight * StackScrollAlgorithm.START_FRACTION,
+ endHeight, fraction));
+ }
+
/**
* Add a listener when the StackY changes. The argument signifies whether an animation is
* needed.
@@ -3910,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)
@@ -5469,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/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 8d500fa..04d3e9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
+
import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Resources;
@@ -28,7 +30,10 @@
import android.os.Trace;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
@@ -48,6 +53,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -156,6 +162,7 @@
private final DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final SessionTracker mSessionTracker;
private final Context mContext;
private final int mWakeUpDelay;
private int mMode;
@@ -273,7 +280,8 @@
ScreenLifecycle screenLifecycle,
AuthController authController,
StatusBarStateController statusBarStateController,
- KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
+ KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+ SessionTracker sessionTracker) {
mContext = context;
mPowerManager = powerManager;
mShadeController = shadeController;
@@ -297,6 +305,7 @@
mAuthController = authController;
mStatusBarStateController = statusBarStateController;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
+ mSessionTracker = sessionTracker;
dumpManager.registerDumpable(getClass().getName(), this);
}
@@ -376,7 +385,7 @@
mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
.setType(MetricsEvent.TYPE_SUCCESS).setSubtype(toSubtype(biometricSourceType)));
Optional.ofNullable(BiometricUiEvent.SUCCESS_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
- .ifPresent(UI_EVENT_LOGGER::log);
+ .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));
boolean unlockAllowed =
mKeyguardStateController.isOccluded()
@@ -641,7 +650,7 @@
mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
.setType(MetricsEvent.TYPE_FAILURE).setSubtype(toSubtype(biometricSourceType)));
Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
- .ifPresent(UI_EVENT_LOGGER::log);
+ .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));
if (biometricSourceType == BiometricSourceType.FINGERPRINT
&& mUpdateMonitor.isUdfpsSupported()) {
@@ -656,7 +665,7 @@
if (mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
startWakeAndUnlock(MODE_SHOW_BOUNCER);
- UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN);
+ UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
mNumConsecutiveFpFailures = 0;
}
}
@@ -670,7 +679,7 @@
.setType(MetricsEvent.TYPE_ERROR).setSubtype(toSubtype(biometricSourceType))
.addTaggedData(MetricsEvent.FIELD_BIOMETRIC_AUTH_ERROR, msgId));
Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
- .ifPresent(UI_EVENT_LOGGER::log);
+ .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId()));
// if we're on the shade and we're locked out, immediately show the bouncer
if (biometricSourceType == BiometricSourceType.FINGERPRINT
@@ -680,7 +689,7 @@
&& (mStatusBarStateController.getState() == StatusBarState.SHADE
|| mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) {
startWakeAndUnlock(MODE_SHOW_BOUNCER);
- UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN);
+ UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
}
cleanup();
}
@@ -786,6 +795,9 @@
return mBiometricType;
}
+ private @Nullable InstanceId getSessionId() {
+ return mSessionTracker.getSessionId(SESSION_KEYGUARD);
+ }
/**
* Translates biometric source type for logging purpose.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index 5f222af..b4e07f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -72,7 +72,10 @@
LOCKSCREEN_NOTIFICATION_FALSE_TOUCH(548),
@UiEvent(doc = "Expand the notification panel while unlocked")
- LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND(549);
+ LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND(549),
+
+ @UiEvent(doc = "Lockscreen > Tap on switch user icon")
+ LOCKSCREEN_SWITCH_USER_TAP(934);
private final int mId;
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 de9933e..9d3f19a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -181,6 +181,7 @@
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.render.NotifPanelEventSource;
import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -204,6 +205,7 @@
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.Utils;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
@@ -225,7 +227,8 @@
import javax.inject.Provider;
@StatusBarComponent.StatusBarScope
-public class NotificationPanelViewController extends PanelViewController {
+public class NotificationPanelViewController extends PanelViewController
+ implements NotifPanelEventSource {
private static final boolean DEBUG = false;
@@ -667,6 +670,8 @@
private Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
+ private final ListenerSet<Callbacks> mNotifEventSourceCallbacks = new ListenerSet<>();
+
private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
@@ -2479,7 +2484,6 @@
mSplitShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
mSplitShadeHeaderController.setShadeExpanded(mQsVisible);
- mKeyguardStatusBarViewController.updateViewState();
if (mCommunalViewController != null) {
mCommunalViewController.updateQsExpansion(qsExpansionFraction);
@@ -3435,6 +3439,28 @@
return mIsLaunchTransitionRunning;
}
+ @Override
+ public void setIsLaunchAnimationRunning(boolean running) {
+ boolean wasRunning = isLaunchTransitionRunning();
+ super.setIsLaunchAnimationRunning(running);
+ if (wasRunning != isLaunchTransitionRunning()) {
+ for (Callbacks cb : mNotifEventSourceCallbacks) {
+ cb.onLaunchingActivityChanged(running);
+ }
+ }
+ }
+
+ @Override
+ protected void setIsClosing(boolean isClosing) {
+ boolean wasClosing = isClosing();
+ super.setIsClosing(isClosing);
+ if (wasClosing != isClosing) {
+ for (Callbacks cb : mNotifEventSourceCallbacks) {
+ cb.onPanelCollapsingChanged(isClosing);
+ }
+ }
+ }
+
public void setLaunchTransitionEndRunnable(Runnable r) {
mLaunchAnimationEndRunnable = r;
}
@@ -4333,6 +4359,16 @@
.commitUpdate(mDisplayId);
}
+ @Override
+ public void registerCallbacks(Callbacks callbacks) {
+ mNotifEventSourceCallbacks.addIfAbsent(callbacks);
+ }
+
+ @Override
+ public void unregisterCallbacks(Callbacks callbacks) {
+ mNotifEventSourceCallbacks.remove(callbacks);
+ }
+
private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
@Override
public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 54d0b03..dc6efba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -506,7 +506,7 @@
private void endClosing() {
if (mClosing) {
- mClosing = false;
+ setIsClosing(false);
onClosingFinished();
}
}
@@ -581,7 +581,7 @@
boolean expandBecauseOfFalsing) {
float target = expand ? getMaxPanelHeight() : 0;
if (!expand) {
- mClosing = true;
+ setIsClosing(true);
}
flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
}
@@ -870,7 +870,7 @@
notifyExpandingStarted();
// Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
- mClosing = true;
+ setIsClosing(true);
if (delayed) {
mNextCollapseSpeedUpFactor = speedUpFactor;
mView.postDelayed(mFlingCollapseRunnable, 120);
@@ -1151,6 +1151,14 @@
mIsLaunchAnimationRunning = running;
}
+ protected void setIsClosing(boolean isClosing) {
+ mClosing = isClosing;
+ }
+
+ protected boolean isClosing() {
+ return mClosing;
+ }
+
public void collapseWithDuration(int animationDuration) {
mFixedDuration = animationDuration;
collapse(false /* delayed */, 1.0f /* speedUpFactor */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 80ae070..c09c3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1544,6 +1544,12 @@
}
private void inflateStatusBarWindow() {
+ if (mStatusBarComponent != null) {
+ // Tear down
+ for (StatusBarComponent.Startable startable : mStatusBarComponent.getStartables()) {
+ startable.stop();
+ }
+ }
mStatusBarComponent = mStatusBarComponentFactory.create();
mFragmentService.addFragmentInstantiationProvider(mStatusBarComponent);
@@ -1572,6 +1578,11 @@
mCommandQueueCallbacks = mStatusBarComponent.getStatusBarCommandQueueCallbacks();
// Connect in to the status bar manager service
mCommandQueue.addCallback(mCommandQueueCallbacks);
+
+ // Perform all other initialization for StatusBarScope
+ for (StatusBarComponent.Startable startable : mStatusBarComponent.getStartables()) {
+ startable.start();
+ }
}
protected void startKeyguard() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
index 61dba92..ad8e79e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
@@ -24,6 +24,7 @@
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.core.StatusBarInitializer;
+import com.android.systemui.statusbar.notification.collection.render.StatusBarNotifPanelEventSourceModule;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
@@ -35,6 +36,7 @@
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
+import java.util.Set;
import javax.inject.Named;
import javax.inject.Scope;
@@ -50,7 +52,10 @@
* that it has many getter methods indicates that we need to access many of these classes from
* outside the component. Should more items be moved *into* this component to avoid so many getters?
*/
-@Subcomponent(modules = {StatusBarViewModule.class})
+@Subcomponent(modules = {
+ StatusBarNotifPanelEventSourceModule.class,
+ StatusBarViewModule.class
+})
@StatusBarComponent.StatusBarScope
public interface StatusBarComponent {
/**
@@ -70,8 +75,7 @@
@interface StatusBarScope {}
/**
- * Creates a {@link NotificationShadeWindowView}/
- * @return
+ * Creates a {@link NotificationShadeWindowView}.
*/
@StatusBarScope
NotificationShadeWindowView getNotificationShadeWindowView();
@@ -138,4 +142,18 @@
*/
@StatusBarScope
StatusBarInitializer getStatusBarInitializer();
+
+ /**
+ * Set of startables to be run after a StatusBarComponent has been constructed.
+ */
+ @StatusBarScope
+ Set<Startable> getStartables();
+
+ /**
+ * Performs initialization logic after {@link StatusBarComponent} has been constructed.
+ */
+ interface Startable {
+ void start();
+ void stop();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 33f2140..d903639 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -23,6 +23,7 @@
import android.hardware.devicestate.DeviceStateManager;
import android.util.Log;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 76615af0..b591545 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -29,6 +29,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
+import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardVisibilityHelper;
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
@@ -49,6 +50,7 @@
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.UserAvatarView;
@@ -82,6 +84,7 @@
private final KeyguardUserDetailAdapter mUserDetailAdapter;
private final FeatureFlags mFeatureFlags;
private final UserSwitchDialogController mUserSwitchDialogController;
+ private final UiEventLogger mUiEventLogger;
private NotificationPanelViewController mNotificationPanelViewController;
private UserAvatarView mUserAvatarView;
UserSwitcherController.UserRecord mCurrentUser;
@@ -133,7 +136,8 @@
Provider<UserDetailView.Adapter> userDetailViewAdapterProvider,
ScreenOffAnimationController screenOffAnimationController,
FeatureFlags featureFlags,
- UserSwitchDialogController userSwitchDialogController) {
+ UserSwitchDialogController userSwitchDialogController,
+ UiEventLogger uiEventLogger) {
super(view);
if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
mContext = context;
@@ -151,6 +155,7 @@
mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider);
mFeatureFlags = featureFlags;
mUserSwitchDialogController = userSwitchDialogController;
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -173,7 +178,10 @@
return;
}
- // Tapping anywhere in the view will open QS user panel
+ // Tapping anywhere in the view will open the user switcher
+ mUiEventLogger.log(
+ LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
+
if (mFeatureFlags.isEnabled(Flags.NEW_USER_SWITCHER)) {
mUserSwitchDialogController.showDialog(mView);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 60938fb..c326835 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -21,6 +21,7 @@
import android.os.UserManager;
import com.android.internal.R;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
@@ -36,7 +37,6 @@
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.DevicePostureControllerImpl;
-import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingsManager;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
import com.android.systemui.statusbar.policy.FlashlightController;
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/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
index 6bc6505..aa671d1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
@@ -20,11 +20,12 @@
import org.mockito.Mockito.verify
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
import com.android.internal.logging.UiEventLogger
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.SessionTracker
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,9 +45,11 @@
@Mock
lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock
- lateinit var dumpManager: DumpManager
- @Mock
lateinit var strongAuthTracker: KeyguardUpdateMonitor.StrongAuthTracker
+ @Mock
+ lateinit var sessionTracker: SessionTracker
+ @Mock
+ lateinit var sessionId: InstanceId
@Captor
lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -58,11 +61,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker)
+ whenever(sessionTracker.getSessionId(anyInt())).thenReturn(sessionId)
keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger(
mContext,
uiEventLogger,
keyguardUpdateMonitor,
- dumpManager)
+ sessionTracker)
}
@Test
@@ -76,7 +80,7 @@
// THEN encrypted / lockdown state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN)
+ .PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN, sessionId)
}
@Test
@@ -93,7 +97,7 @@
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_TIMEOUT)
+ .PRIMARY_AUTH_REQUIRED_TIMEOUT, sessionId)
}
@Test
@@ -110,7 +114,7 @@
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+ .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE, sessionId)
}
@Test
@@ -128,9 +132,9 @@
// THEN primary auth required state is logged with all the reasons
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_TIMEOUT)
+ .PRIMARY_AUTH_REQUIRED_TIMEOUT, sessionId)
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE)
+ .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE, sessionId)
// WHEN onStrongAuthStateChanged is called again
updateMonitorCallback.onStrongAuthStateChanged(0)
@@ -152,7 +156,7 @@
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT)
+ .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT, sessionId)
// WHEN face lockout is reset
whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(false)
@@ -160,7 +164,7 @@
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET)
+ .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET, sessionId)
}
@Test
@@ -176,7 +180,7 @@
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT)
+ .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT, sessionId)
// WHEN fingerprint lockout is reset
whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
@@ -184,7 +188,7 @@
// THEN primary auth required state is logged
verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent
- .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET)
+ .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET, sessionId)
}
fun captureUpdateMonitorCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 599e547..a819a7a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -49,6 +49,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -120,6 +121,8 @@
private FeatureFlags mFeatureFlags;
@Mock
private UserSwitcherController mUserSwitcherController;
+ @Mock
+ private SessionTracker mSessionTracker;
private Configuration mConfiguration;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@@ -154,7 +157,8 @@
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, mKeyguardSecurityViewFlipperController,
mConfigurationController, mFalsingCollector, mFalsingManager,
- mUserSwitcherController, mFeatureFlags, mGlobalSettings).create(mSecurityCallback);
+ mUserSwitcherController, mFeatureFlags, mGlobalSettings,
+ mSessionTracker).create(mSecurityCallback);
}
@Test
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 25dd23a..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
@@ -112,7 +112,7 @@
private CollectionReadyForBuildListener mReadyForBuildListener;
private List<NotificationEntryBuilder> mPendingSet = new ArrayList<>();
private List<NotificationEntry> mEntrySet = new ArrayList<>();
- private List<ListEntry> mBuiltList;
+ private List<ListEntry> mBuiltList = new ArrayList<>();
private TestableStabilityManager mStabilityManager;
private TestableNotifFilter mFinalizeFilter;
@@ -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);
@@ -1642,6 +1686,19 @@
}
@Test
+ public void testPipelineRunDisallowedDueToVisualStability() {
+ // GIVEN pipeline run not allowed due to visual stability
+ mStabilityManager.setAllowPipelineRun(false);
+
+ // WHEN we try to run the pipeline with a change
+ addNotif(0, PACKAGE_1);
+ dispatchBuild();
+
+ // THEN there is no change; the pipeline did not run
+ verifyBuiltList();
+ }
+
+ @Test
public void testIsSorted() {
Comparator<Integer> intCmp = Integer::compare;
assertTrue(ShadeListBuilder.isSorted(Collections.emptyList(), intCmp));
@@ -2037,6 +2094,7 @@
}
private static class TestableStabilityManager extends NotifStabilityManager {
+ boolean mAllowPipelineRun = true;
boolean mAllowGroupChanges = true;
boolean mAllowSectionChanges = true;
boolean mAllowEntryReodering = true;
@@ -2060,6 +2118,15 @@
return this;
}
+ TestableStabilityManager setAllowPipelineRun(boolean allowPipelineRun) {
+ mAllowPipelineRun = allowPipelineRun;
+ return this;
+ }
+
+ @Override
+ public boolean isPipelineRunAllowed() {
+ return mAllowPipelineRun;
+ }
@Override
public void onBeginRun() {
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/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 5df1d28..17b3b1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
+import com.android.systemui.statusbar.notification.collection.render.NotifPanelEventSource;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -65,9 +66,11 @@
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
@Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private NotifPanelEventSource mNotifPanelEventSource;
@Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
+ @Captor private ArgumentCaptor<NotifPanelEventSource.Callbacks> mNotifPanelEventsCallbackCaptor;
@Captor private ArgumentCaptor<NotifStabilityManager> mNotifStabilityManagerCaptor;
private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -75,6 +78,7 @@
private WakefulnessLifecycle.Observer mWakefulnessObserver;
private StatusBarStateController.StateListener mStatusBarStateListener;
+ private NotifPanelEventSource.Callbacks mNotifPanelEventsCallback;
private NotifStabilityManager mNotifStabilityManager;
private NotificationEntry mEntry;
@@ -83,11 +87,12 @@
MockitoAnnotations.initMocks(this);
mCoordinator = new VisualStabilityCoordinator(
+ mFakeExecutor,
mDumpManager,
mHeadsUpManager,
- mWakefulnessLifecycle,
+ mNotifPanelEventSource,
mStatusBarStateController,
- mFakeExecutor);
+ mWakefulnessLifecycle);
mCoordinator.attach(mNotifPipeline);
@@ -98,6 +103,9 @@
verify(mStatusBarStateController).addCallback(mSBStateListenerCaptor.capture());
mStatusBarStateListener = mSBStateListenerCaptor.getValue();
+ verify(mNotifPanelEventSource).registerCallbacks(mNotifPanelEventsCallbackCaptor.capture());
+ mNotifPanelEventsCallback = mNotifPanelEventsCallbackCaptor.getValue();
+
verify(mNotifPipeline).setVisualStabilityManager(mNotifStabilityManagerCaptor.capture());
mNotifStabilityManager = mNotifStabilityManagerCaptor.getValue();
mNotifStabilityManager.setInvalidationListener(mInvalidateListener);
@@ -319,6 +327,58 @@
}
@Test
+ public void testNotLaunchingActivityAnymore_invalidationCalled() {
+ // GIVEN visual stability is being maintained b/c animation is playing
+ setActivityLaunching(true);
+
+ assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
+
+ // WHEN the animation has stopped playing
+ setActivityLaunching(false);
+
+ // invalidate is called, b/c we were previously suppressing the pipeline from running
+ verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager);
+ }
+
+ @Test
+ public void testNotCollapsingPanelAnymore_invalidationCalled() {
+ // GIVEN visual stability is being maintained b/c animation is playing
+ setPanelCollapsing(true);
+
+ assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
+
+ // WHEN the animation has stopped playing
+ setPanelCollapsing(false);
+
+ // invalidate is called, b/c we were previously suppressing the pipeline from running
+ verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager);
+ }
+
+ @Test
+ public void testNeverSuppressPipelineRunFromPanelCollapse_noInvalidationCalled() {
+ // GIVEN animation is playing
+ setPanelCollapsing(true);
+
+ // WHEN the animation has stopped playing
+ setPanelCollapsing(false);
+
+ // THEN invalidate is not called, b/c nothing has been suppressed
+ verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager);
+ }
+
+ @Test
+ public void testNeverSuppressPipelineRunFromLaunchActivity_noInvalidationCalled() {
+ // GIVEN animation is playing
+ setActivityLaunching(true);
+
+ // WHEN the animation has stopped playing
+ setActivityLaunching(false);
+
+ // THEN invalidate is not called, b/c nothing has been suppressed
+ verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager);
+ }
+
+ @Test
public void testNotSuppressingEntryReorderingAnymoreWillInvalidate() {
// GIVEN visual stability is being maintained b/c panel is expanded
setPulsing(false);
@@ -370,6 +430,14 @@
}
+ private void setActivityLaunching(boolean activityLaunching) {
+ mNotifPanelEventsCallback.onLaunchingActivityChanged(activityLaunching);
+ }
+
+ private void setPanelCollapsing(boolean collapsing) {
+ mNotifPanelEventsCallback.onPanelCollapsingChanged(collapsing);
+ }
+
private void setPulsing(boolean pulsing) {
mStatusBarStateListener.onPulsingChanged(pulsing);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
new file mode 100644
index 0000000..0909ff2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt
@@ -0,0 +1,38 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.R
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for {@link MediaContainView}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class MediaContainerViewTest : SysuiTestCase() {
+
+ lateinit var mediaContainerView : MediaContainerView
+
+ @Before
+ fun setUp() {
+ mediaContainerView = LayoutInflater.from(context).inflate(
+ R.layout.keyguard_media_container, null, false) as MediaContainerView
+ }
+
+ @Test
+ fun testUpdateClipping_updatesClipHeight() {
+ assertTrue(mediaContainerView.clipHeight == 0)
+
+ mediaContainerView.actualHeight = 10
+ mediaContainerView.updateClipping()
+ assertTrue(mediaContainerView.clipHeight == 10)
+ }
+}
\ No newline at end of file
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/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 46ba097..bdcbbbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -27,6 +27,7 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
@@ -44,6 +45,7 @@
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.MathUtils;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
@@ -165,6 +167,47 @@
}
@Test
+ public void testUpdateStackEndHeight_forEndOfStackHeightAnimation() {
+ final float nsslHeight = 10f;
+ final float bottomMargin = 1f;
+ final float topPadding = 1f;
+
+ mStackScroller.updateStackEndHeight(nsslHeight, bottomMargin, topPadding);
+ final float stackEndHeight = nsslHeight - bottomMargin - topPadding;
+ assertTrue(mAmbientState.getStackEndHeight() == stackEndHeight);
+ }
+
+ @Test
+ public void testUpdateStackHeight_withDozeAmount_whenDozeChanging() {
+ final float dozeAmount = 0.5f;
+ mAmbientState.setDozeAmount(dozeAmount);
+
+ final float endHeight = 8f;
+ final float expansionFraction = 1f;
+ float expected = MathUtils.lerp(
+ endHeight * StackScrollAlgorithm.START_FRACTION,
+ endHeight, dozeAmount);
+
+ mStackScroller.updateStackHeight(endHeight, expansionFraction);
+ assertTrue(mAmbientState.getStackHeight() == expected);
+ }
+
+ @Test
+ public void testUpdateStackHeight_withExpansionAmount_whenDozeNotChanging() {
+ final float dozeAmount = 1f;
+ mAmbientState.setDozeAmount(dozeAmount);
+
+ final float endHeight = 8f;
+ final float expansionFraction = 0.5f;
+ final float expected = MathUtils.lerp(
+ endHeight * StackScrollAlgorithm.START_FRACTION,
+ endHeight, expansionFraction);
+
+ mStackScroller.updateStackHeight(endHeight, expansionFraction);
+ assertTrue(mAmbientState.getStackHeight() == expected);
+ }
+
+ @Test
public void testNotDimmedOnKeyguard() {
when(mBarState.getState()).thenReturn(StatusBarState.SHADE);
mStackScroller.setDimmed(true /* dimmed */, false /* animate */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 5ca1f21..8c7d22d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -107,6 +108,8 @@
private StatusBarStateController mStatusBarStateController;
@Mock
private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+ @Mock
+ private SessionTracker mSessionTracker;
private BiometricUnlockController mBiometricUnlockController;
@Before
@@ -129,7 +132,8 @@
mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
mMetricsLogger, mDumpManager, mPowerManager,
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
- mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController);
+ mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController,
+ mSessionTracker);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
}
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/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index 6364d2f..48b1732 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -36,6 +36,7 @@
import com.android.internal.R;
import com.android.internal.view.RotationPolicy;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
new file mode 100644
index 0000000..a57f6a1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.ViewUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.CommunalStateController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger
+import com.android.systemui.statusbar.phone.NotificationPanelViewController
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+import javax.inject.Provider
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class KeyguardQsUserSwitchControllerTest : SysuiTestCase() {
+ @Mock
+ private lateinit var screenLifecycle: ScreenLifecycle
+
+ @Mock
+ private lateinit var userSwitcherController: UserSwitcherController
+
+ @Mock
+ private lateinit var communalStateController: CommunalStateController
+
+ @Mock
+ private lateinit var keyguardStateController: KeyguardStateController
+
+ @Mock
+ private lateinit var falsingManager: FalsingManager
+
+ @Mock
+ private lateinit var configurationController: ConfigurationController
+
+ @Mock
+ private lateinit var statusBarStateController: SysuiStatusBarStateController
+
+ @Mock
+ private lateinit var dozeParameters: DozeParameters
+
+ @Mock
+ private lateinit var userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>
+
+ @Mock
+ private lateinit var screenOffAnimationController: ScreenOffAnimationController
+
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
+
+ @Mock
+ private lateinit var userSwitchDialogController: UserSwitchDialogController
+
+ @Mock
+ private lateinit var uiEventLogger: UiEventLogger
+
+ @Mock
+ private lateinit var notificationPanelViewController: NotificationPanelViewController
+
+ private lateinit var view: FrameLayout
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ view = LayoutInflater.from(context)
+ .inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout
+
+ keyguardQsUserSwitchController = KeyguardQsUserSwitchController(
+ view,
+ context,
+ context.resources,
+ screenLifecycle,
+ userSwitcherController,
+ communalStateController,
+ keyguardStateController,
+ falsingManager,
+ configurationController,
+ statusBarStateController,
+ dozeParameters,
+ userDetailViewAdapterProvider,
+ screenOffAnimationController,
+ featureFlags,
+ userSwitchDialogController,
+ uiEventLogger)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+ keyguardQsUserSwitchController
+ .setNotificationPanelViewController(notificationPanelViewController)
+ `when`(userSwitcherController.keyguardStateController).thenReturn(keyguardStateController)
+ `when`(userSwitcherController.keyguardStateController.isShowing).thenReturn(true)
+ keyguardQsUserSwitchController.init()
+ }
+
+ @After
+ fun tearDown() {
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun testUiEventLogged() {
+ view.findViewById<View>(R.id.kg_multi_user_avatar)?.performClick()
+ verify(uiEventLogger, times(1))
+ .log(LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP)
+ }
+}
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/proto/src/system_messages.proto b/proto/src/system_messages.proto
index e89dda9..177b080 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -282,6 +282,10 @@
// Notify the user to set up dream
NOTE_SETUP_DREAM = 68;
+ // Inform the user that MTE override is active.
+ // Package: android
+ NOTE_MTE_OVERRIDE_ENABLED = 69;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
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/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 75acf81..bb49ba0 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -39,6 +39,7 @@
import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
/**
@@ -61,6 +62,7 @@
private final ArraySet<ComponentName> mAllowedActivities;
@Nullable
private final ArraySet<ComponentName> mBlockedActivities;
+ private Consumer<ActivityInfo> mActivityBlockedCallback;
@NonNull
final ArraySet<Integer> mRunningUids = new ArraySet<>();
@@ -81,10 +83,12 @@
@NonNull ArraySet<UserHandle> allowedUsers,
@Nullable Set<ComponentName> allowedActivities,
@Nullable Set<ComponentName> blockedActivities,
- @NonNull ActivityListener activityListener) {
+ @NonNull ActivityListener activityListener,
+ @NonNull Consumer<ActivityInfo> activityBlockedCallback) {
mAllowedUsers = allowedUsers;
mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities);
mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities);
+ mActivityBlockedCallback = activityBlockedCallback;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
}
@@ -96,6 +100,7 @@
for (int i = 0; i < activityCount; i++) {
final ActivityInfo aInfo = activities.get(i);
if (!canContainActivity(aInfo, /* windowFlags= */ 0, /* systemWindowFlags= */ 0)) {
+ mActivityBlockedCallback.accept(aInfo);
return false;
}
}
@@ -105,7 +110,11 @@
@Override
public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
int systemWindowFlags) {
- return canContainActivity(activityInfo, windowFlags, systemWindowFlags);
+ if (!canContainActivity(activityInfo, windowFlags, systemWindowFlags)) {
+ mActivityBlockedCallback.accept(activityInfo);
+ return false;
+ }
+ return true;
}
@Override
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/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 95b9e58..98a5ec1 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -34,6 +34,8 @@
import android.companion.virtual.VirtualDeviceParams;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.display.DisplayManager;
@@ -57,6 +59,7 @@
import android.window.DisplayWindowPolicyController;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.BlockedAppActivity;
import com.android.server.LocalServices;
import java.io.FileDescriptor;
@@ -418,7 +421,8 @@
getAllowedUserHandles(),
mParams.getAllowedActivities(),
mParams.getBlockedActivities(),
- createListenerAdapter(displayId));
+ createListenerAdapter(displayId),
+ activityInfo -> onActivityBlocked(displayId, activityInfo));
mWindowPolicyControllers.put(displayId, dwpc);
return dwpc;
}
@@ -441,6 +445,16 @@
}
}
+ private void onActivityBlocked(int displayId, ActivityInfo activityInfo) {
+ Intent intent = BlockedAppActivity.createStreamingBlockedIntent(
+ UserHandle.getUserId(activityInfo.applicationInfo.uid), activityInfo,
+ mAssociationInfo.getDisplayName());
+ mContext.startActivityAsUser(
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+ ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
+ mContext.getUser());
+ }
+
private ArraySet<UserHandle> getAllowedUserHandles() {
ArraySet<UserHandle> result = new ArraySet<>();
DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index f56bfab..a8eeaf8 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -901,32 +901,20 @@
/**
* Perform the given action for each package.
- *
- * @param locked whether to hold the packages lock. If the lock is not held, the objects will
- * be iterated using a temporary data structure. In the vast majority of cases,
- * the lock should not have to be held. This is exposed to mirror the
- * functionality of the other forEach methods, for eventual migration.
* @param action action to be performed
*/
- public abstract void forEachPackageState(boolean locked, Consumer<PackageStateInternal> action);
+ public abstract void forEachPackageState(Consumer<PackageStateInternal> action);
/**
- * {@link #forEachPackageState(boolean, Consumer)} but filtered to only states with packages
+ * {@link #forEachPackageState(Consumer)} but filtered to only states with packages
* on device where {@link PackageStateInternal#getPkg()} is not null.
*/
public abstract void forEachPackage(Consumer<AndroidPackage> action);
/**
* Perform the given action for each installed package for a user.
- * Note that packages lock will be held while performing the actions.
*/
public abstract void forEachInstalledPackage(
- @NonNull Consumer<AndroidPackage> actionLocked, @UserIdInt int userId);
-
- /**
- * Perform the given action for each installed package for a user.
- */
- public abstract void forEachInstalledPackage(boolean locked,
@NonNull Consumer<AndroidPackage> action, @UserIdInt int userId);
/** Returns the list of enabled components */
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index efdd7ab..4129feb 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -10,9 +10,6 @@
# Userspace reboot
per-file UserspaceRebootLogger.java = ioffe@google.com, dvander@google.com
-# Sensor Privacy
-per-file SensorPrivacyService.java = file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
-
# ServiceWatcher
per-file ServiceWatcher.java = sooniln@google.com
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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index f71f02a..8aeae6a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -123,6 +123,7 @@
import android.provider.Downloads;
import android.provider.MediaStore;
import android.provider.Settings;
+import android.service.storage.ExternalStorageService;
import android.sysprop.VoldProperties;
import android.text.TextUtils;
import android.text.format.DateUtils;
@@ -491,6 +492,8 @@
@GuardedBy("mAppFuseLock")
private AppFuseBridge mAppFuseBridge = null;
+ private HashMap<Integer, Integer> mUserSharesMediaWith = new HashMap<>();
+
/** Matches known application dir paths. The first group contains the generic part of the path,
* the second group contains the user id (or null if it's a public volume without users), the
* third group contains the package name, and the fourth group the remainder of the path.
@@ -1235,6 +1238,21 @@
private void onUnlockUser(int userId) {
Slog.d(TAG, "onUnlockUser " + userId);
+ if (userId != UserHandle.USER_SYSTEM) {
+ // Check if this user shares media with another user
+ try {
+ Context userContext = mContext.createPackageContextAsUser("system", 0,
+ UserHandle.of(userId));
+ UserManager um = userContext.getSystemService(UserManager.class);
+ if (um != null && um.isMediaSharedWithParent()) {
+ int parentUserId = um.getProfileParent(userId).id;
+ mUserSharesMediaWith.put(userId, parentUserId);
+ mUserSharesMediaWith.put(parentUserId, userId);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Failed to create user context for user " + userId);
+ }
+ }
// We purposefully block here to make sure that user-specific
// staging area is ready so it's ready for zygote-forked apps to
// bind mount against.
@@ -3971,6 +3989,29 @@
final boolean realState = (flags & StorageManager.FLAG_REAL_STATE) != 0;
final boolean includeInvisible = (flags & StorageManager.FLAG_INCLUDE_INVISIBLE) != 0;
final boolean includeRecent = (flags & StorageManager.FLAG_INCLUDE_RECENT) != 0;
+ final boolean includeSharedProfile =
+ (flags & StorageManager.FLAG_INCLUDE_SHARED_PROFILE) != 0;
+
+ // Only Apps with MANAGE_EXTERNAL_STORAGE should call the API with includeSharedProfile
+ if (includeSharedProfile) {
+ try {
+ // Get package name for calling app and
+ // verify it has MANAGE_EXTERNAL_STORAGE permission
+ final String[] packagesFromUid = mIPackageManager.getPackagesForUid(callingUid);
+ if (packagesFromUid == null) {
+ throw new SecurityException("Unknown uid " + callingUid);
+ }
+ // Checking first entry in packagesFromUid is enough as using "sharedUserId"
+ // mechanism is rare and discouraged. Also, Apps that share same UID share the same
+ // permissions.
+ if (!mStorageManagerInternal.hasExternalStorageAccess(callingUid,
+ packagesFromUid[0])) {
+ throw new SecurityException("Only File Manager Apps permitted");
+ }
+ } catch (RemoteException re) {
+ throw new SecurityException("Unknown uid " + callingUid, re);
+ }
+ }
// Report all volumes as unmounted until we've recorded that user 0 has unlocked. There
// are no guarantees that callers will see a consistent view of the volume before that
@@ -4002,6 +4043,7 @@
final ArrayList<StorageVolume> res = new ArrayList<>();
final ArraySet<String> resUuids = new ArraySet<>();
+ final int userIdSharingMedia = mUserSharesMediaWith.getOrDefault(userId, -1);
synchronized (mLock) {
for (int i = 0; i < mVolumes.size(); i++) {
final String volId = mVolumes.keyAt(i);
@@ -4014,6 +4056,11 @@
if (vol.getMountUserId() == userId) {
break;
}
+ if (includeSharedProfile && vol.getMountUserId() == userIdSharingMedia) {
+ // If the volume belongs to a user we share media with,
+ // return it too.
+ break;
+ }
// Skip if emulated volume not for userId
default:
continue;
@@ -4021,10 +4068,12 @@
boolean match = false;
if (forWrite) {
- match = vol.isVisibleForWrite(userId);
+ match = vol.isVisibleForWrite(userId)
+ || (includeSharedProfile && vol.isVisibleForWrite(userIdSharingMedia));
} else {
match = vol.isVisibleForUser(userId)
- || (includeInvisible && vol.getPath() != null);
+ || (includeInvisible && vol.getPath() != null)
+ || (includeSharedProfile && vol.isVisibleForRead(userIdSharingMedia));
}
if (!match) continue;
@@ -4045,9 +4094,13 @@
reportUnmounted = true;
}
- final StorageVolume userVol = vol.buildStorageVolume(mContext, userId,
+ int volUserId = userId;
+ if (volUserId != vol.getMountUserId() && vol.getMountUserId() >= 0) {
+ volUserId = vol.getMountUserId();
+ }
+ final StorageVolume userVol = vol.buildStorageVolume(mContext, volUserId,
reportUnmounted);
- if (vol.isPrimary()) {
+ if (vol.isPrimary() && vol.getMountUserId() == userId) {
res.add(0, userVol);
foundPrimary = true;
} else {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ae4f79e7..902659c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5014,6 +5014,7 @@
}
// UART is on if init's console service is running, send a warning notification.
showConsoleNotificationIfActive();
+ showMteOverrideNotificationIfActive();
t.traceEnd();
}
@@ -5048,6 +5049,35 @@
}
+ private void showMteOverrideNotificationIfActive() {
+ if (!SystemProperties.getBoolean("ro.arm64.memtag.bootctl_supported", false)
+ || !com.android.internal.os.Zygote.nativeSupportsMemoryTagging()) {
+ return;
+ }
+ String title = mContext
+ .getString(com.android.internal.R.string.mte_override_notification_title);
+ String message = mContext
+ .getString(com.android.internal.R.string.mte_override_notification_message);
+ Notification notification =
+ new Notification.Builder(mContext, SystemNotificationChannels.DEVELOPER)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setOngoing(true)
+ .setTicker(title)
+ .setDefaults(0) // please be quiet
+ .setColor(mContext.getColor(
+ com.android.internal.R.color
+ .system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(message)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .build();
+
+ NotificationManager notificationManager =
+ mContext.getSystemService(NotificationManager.class);
+ notificationManager.notifyAsUser(
+ null, SystemMessage.NOTE_MTE_OVERRIDE_ENABLED, notification, UserHandle.ALL);
+ }
+
@Override
public void bootAnimationComplete() {
if (DEBUG_ALL) Slog.d(TAG, "bootAnimationComplete: Callers=" + Debug.getCallers(4));
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/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index bd63a24..1315293 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -1314,7 +1314,7 @@
void onSystemReady() {
mContext.registerReceiverForAllUsers(mActionButtonReceiver,
new IntentFilter(ACTION_FGS_MANAGER_TRAMPOLINE),
- MANAGE_ACTIVITY_TASKS, mBgController.mBgHandler);
+ MANAGE_ACTIVITY_TASKS, mBgController.mBgHandler, Context.RECEIVER_NOT_EXPORTED);
}
void postRequestBgRestrictedIfNecessary(String packageName, int uid) {
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/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index f1429a5..3c9d29d 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -85,6 +85,7 @@
import com.android.server.SystemService.TargetUser;
import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.List;
/**
@@ -159,6 +160,29 @@
new GameManagerShellCommand().exec(this, in, out, err, args, callback, result);
}
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ writer.println("Permission Denial: can't dump GameManagerService from from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " without permission " + android.Manifest.permission.DUMP);
+ return;
+ }
+ if (args == null || args.length == 0) {
+ writer.println("*Dump GameManagerService*");
+ dumpAllGameConfigs(writer);
+ }
+ }
+
+ private void dumpAllGameConfigs(PrintWriter pw) {
+ final int userId = ActivityManager.getCurrentUser();
+ String[] packageList = getInstalledGamePackageNames(userId);
+ for (final String packageName : packageList) {
+ pw.println(getInterventionList(packageName));
+ }
+ }
+
class SettingsHandler extends Handler {
SettingsHandler(Looper looper) {
@@ -1266,8 +1290,7 @@
.append(packageName);
return listStrSb.toString();
}
- listStrSb.append("\nPackage name: ")
- .append(packageName)
+ listStrSb.append("\n")
.append(packageConfig.toString());
return listStrSb.toString();
}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index 9d4d1c1..366718c 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -38,6 +38,7 @@
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
+import android.apphibernation.HibernationStats;
import android.apphibernation.IAppHibernationService;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -221,7 +222,7 @@
}
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION,
- "Caller does not have MANAGE_APP_HIBERNATION permission.");
+ "Caller did not have permission while calling " + methodName);
userId = handleIncomingUser(userId, methodName);
synchronized (mLock) {
if (!checkUserStatesExist(userId, methodName)) {
@@ -380,6 +381,46 @@
}
/**
+ * Return the stats from app hibernation for each package provided.
+ *
+ * @param packageNames the set of packages to return stats for. Returns all if null
+ * @return map from package to stats for that package
+ */
+ public Map<String, HibernationStats> getHibernationStatsForUser(
+ @Nullable Set<String> packageNames, int userId) {
+ Map<String, HibernationStats> statsMap = new ArrayMap<>();
+ String methodName = "getHibernationStatsForUser";
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_APP_HIBERNATION,
+ "Caller does not have MANAGE_APP_HIBERNATION permission.");
+ userId = handleIncomingUser(userId, methodName);
+ synchronized (mLock) {
+ if (!checkUserStatesExist(userId, methodName)) {
+ return statsMap;
+ }
+ final Map<String, UserLevelState> userPackageStates = mUserStates.get(userId);
+ Set<String> pkgs = packageNames != null ? packageNames : userPackageStates.keySet();
+ for (String pkgName : pkgs) {
+ if (!mPackageManagerInternal.canQueryPackage(Binder.getCallingUid(), pkgName)) {
+ // Package not visible to caller
+ continue;
+ }
+ if (!mGlobalHibernationStates.containsKey(pkgName)
+ || !userPackageStates.containsKey(pkgName)) {
+ Slog.w(TAG, String.format(
+ "No hibernation state associated with package %s user %d. Maybe"
+ + "the package was uninstalled? ", pkgName, userId));
+ continue;
+ }
+ HibernationStats stats = new HibernationStats(
+ mGlobalHibernationStates.get(pkgName).savedByte);
+ statsMap.put(pkgName, stats);
+ }
+ }
+ return statsMap;
+ }
+
+ /**
* Put an app into hibernation for a given user, allowing user-level optimizations to occur. Do
* not hold {@link #mLock} while calling this to avoid deadlock scenarios.
*/
@@ -788,6 +829,13 @@
}
@Override
+ public Map<String, HibernationStats> getHibernationStatsForUser(
+ @Nullable List<String> packageNames, int userId) {
+ Set<String> pkgsSet = packageNames != null ? new ArraySet<>(packageNames) : null;
+ return mService.getHibernationStatsForUser(pkgsSet, userId);
+ }
+
+ @Override
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
@Nullable FileDescriptor err, @NonNull String[] args,
@Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) {
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/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
new file mode 100644
index 0000000..767b2d1
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.content.Context;
+import android.hardware.display.BrightnessInfo;
+import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.util.Slog;
+
+import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
+
+import java.io.PrintWriter;
+
+/**
+ * This class monitors various conditions, such as skin temperature throttling status, and limits
+ * the allowed brightness range accordingly.
+ */
+class BrightnessThrottler {
+ private static final String TAG = "BrightnessThrottler";
+ private static final boolean DEBUG = false;
+
+ private static final int THROTTLING_INVALID = -1;
+
+ private final Injector mInjector;
+ private final Handler mHandler;
+ private BrightnessThrottlingData mThrottlingData;
+ private final Runnable mThrottlingChangeCallback;
+ private final SkinThermalStatusObserver mSkinThermalStatusObserver;
+ private int mThrottlingStatus;
+ private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+ private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason =
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+
+ BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData,
+ Runnable throttlingChangeCallback) {
+ this(new Injector(), handler, throttlingData, throttlingChangeCallback);
+ }
+
+ BrightnessThrottler(Injector injector, Handler handler, BrightnessThrottlingData throttlingData,
+ Runnable throttlingChangeCallback) {
+ mInjector = injector;
+ mHandler = handler;
+ mThrottlingData = throttlingData;
+ mThrottlingChangeCallback = throttlingChangeCallback;
+ mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
+
+ resetThrottlingData(mThrottlingData);
+ }
+
+ boolean deviceSupportsThrottling() {
+ return mThrottlingData != null;
+ }
+
+ float getBrightnessCap() {
+ return mBrightnessCap;
+ }
+
+ int getBrightnessMaxReason() {
+ return mBrightnessMaxReason;
+ }
+
+ boolean isThrottled() {
+ return mBrightnessMaxReason != BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+ }
+
+ void stop() {
+ mSkinThermalStatusObserver.stopObserving();
+
+ // We're asked to stop throttling, so reset brightness restrictions.
+ mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+ mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+
+ // We set throttling status to an invalid value here so that we act on the first throttling
+ // value received from the thermal service after registration, even if that throttling value
+ // is THROTTLING_NONE.
+ mThrottlingStatus = THROTTLING_INVALID;
+ }
+
+ void resetThrottlingData(BrightnessThrottlingData throttlingData) {
+ stop();
+ mThrottlingData = throttlingData;
+
+ if (deviceSupportsThrottling()) {
+ mSkinThermalStatusObserver.startObserving();
+ }
+ }
+
+ private float verifyAndConstrainBrightnessCap(float brightness) {
+ if (brightness < PowerManager.BRIGHTNESS_MIN) {
+ Slog.e(TAG, "brightness " + brightness + " is lower than the minimum possible "
+ + "brightness " + PowerManager.BRIGHTNESS_MIN);
+ brightness = PowerManager.BRIGHTNESS_MIN;
+ }
+
+ if (brightness > PowerManager.BRIGHTNESS_MAX) {
+ Slog.e(TAG, "brightness " + brightness + " is higher than the maximum possible "
+ + "brightness " + PowerManager.BRIGHTNESS_MAX);
+ brightness = PowerManager.BRIGHTNESS_MAX;
+ }
+
+ return brightness;
+ }
+
+ private void thermalStatusChanged(@Temperature.ThrottlingStatus int newStatus) {
+ if (mThrottlingStatus != newStatus) {
+ mThrottlingStatus = newStatus;
+ updateThrottling();
+ }
+ }
+
+ private void updateThrottling() {
+ if (!deviceSupportsThrottling()) {
+ return;
+ }
+
+ float brightnessCap = PowerManager.BRIGHTNESS_MAX;
+ int brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+
+ if (mThrottlingStatus != THROTTLING_INVALID) {
+ // Throttling levels are sorted by increasing severity
+ for (ThrottlingLevel level : mThrottlingData.throttlingLevels) {
+ if (level.thermalStatus <= mThrottlingStatus) {
+ brightnessCap = level.brightness;
+ brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
+ } else {
+ // Throttling levels that are greater than the current status are irrelevant
+ break;
+ }
+ }
+ }
+
+ if (mBrightnessCap != brightnessCap || mBrightnessMaxReason != brightnessMaxReason) {
+ mBrightnessCap = verifyAndConstrainBrightnessCap(brightnessCap);
+ mBrightnessMaxReason = brightnessMaxReason;
+
+ if (DEBUG) {
+ Slog.d(TAG, "State changed: mBrightnessCap = " + mBrightnessCap
+ + ", mBrightnessMaxReason = "
+ + BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
+ }
+
+ if (mThrottlingChangeCallback != null) {
+ mThrottlingChangeCallback.run();
+ }
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
+ }
+
+ private void dumpLocal(PrintWriter pw) {
+ pw.println("BrightnessThrottler:");
+ pw.println(" mThrottlingData=" + mThrottlingData);
+ pw.println(" mThrottlingStatus=" + mThrottlingStatus);
+ pw.println(" mBrightnessCap=" + mBrightnessCap);
+ pw.println(" mBrightnessMaxReason=" +
+ BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
+
+ mSkinThermalStatusObserver.dump(pw);
+ }
+
+ private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
+ private final Injector mInjector;
+ private final Handler mHandler;
+
+ private IThermalService mThermalService;
+ private boolean mStarted;
+
+ SkinThermalStatusObserver(Injector injector, Handler handler) {
+ mInjector = injector;
+ mHandler = handler;
+ }
+
+ @Override
+ public void notifyThrottling(Temperature temp) {
+ if (DEBUG) {
+ Slog.d(TAG, "New thermal throttling status = " + temp.getStatus());
+ }
+ mHandler.post(() -> {
+ final @Temperature.ThrottlingStatus int status = temp.getStatus();
+ thermalStatusChanged(status);
+ });
+ }
+
+ void startObserving() {
+ if (mStarted) {
+ if (DEBUG) {
+ Slog.d(TAG, "Thermal status observer already started");
+ }
+ return;
+ }
+ mThermalService = mInjector.getThermalService();
+ if (mThermalService == null) {
+ Slog.e(TAG, "Could not observe thermal status. Service not available");
+ return;
+ }
+ try {
+ // We get a callback immediately upon registering so there's no need to query
+ // for the current value.
+ mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
+ mStarted = true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register thermal status listener", e);
+ }
+ }
+
+ void stopObserving() {
+ if (!mStarted) {
+ if (DEBUG) {
+ Slog.d(TAG, "Stop skipped because thermal status observer not started");
+ }
+ return;
+ }
+ try {
+ mThermalService.unregisterThermalEventListener(this);
+ mStarted = false;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to unregister thermal status listener", e);
+ }
+ mThermalService = null;
+ }
+
+ void dump(PrintWriter writer) {
+ writer.println(" SkinThermalStatusObserver:");
+ writer.println(" mStarted: " + mStarted);
+ if (mThermalService != null) {
+ writer.println(" ThermalService available");
+ } else {
+ writer.println(" ThermalService not available");
+ }
+ }
+ }
+
+ public static class Injector {
+ public IThermalService getThermalService() {
+ return IThermalService.Stub.asInterface(
+ ServiceManager.getService(Context.THERMAL_SERVICE));
+ }
+ }
+}
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/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index f3969b1..a4a6eb4 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -33,6 +33,8 @@
import com.android.internal.R;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.BrightnessThresholds;
+import com.android.server.display.config.BrightnessThrottlingMap;
+import com.android.server.display.config.BrightnessThrottlingPoint;
import com.android.server.display.config.Density;
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.DisplayQuirks;
@@ -43,6 +45,7 @@
import com.android.server.display.config.RefreshRateRange;
import com.android.server.display.config.SensorDetails;
import com.android.server.display.config.ThermalStatus;
+import com.android.server.display.config.ThermalThrottling;
import com.android.server.display.config.Thresholds;
import com.android.server.display.config.XmlParser;
@@ -145,6 +148,8 @@
private DensityMap mDensityMap;
private String mLoadedFrom = null;
+ private BrightnessThrottlingData mBrightnessThrottlingData;
+
private DisplayDeviceConfig(Context context) {
mContext = context;
}
@@ -424,6 +429,13 @@
return mDensityMap;
}
+ /**
+ * @return brightness throttling data configuration data for the display.
+ */
+ public BrightnessThrottlingData getBrightnessThrottlingData() {
+ return BrightnessThrottlingData.create(mBrightnessThrottlingData);
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -441,6 +453,7 @@
+ ", mQuirks=" + mQuirks
+ ", isHbmEnabled=" + mIsHighBrightnessModeEnabled
+ ", mHbmData=" + mHbmData
+ + ", mBrightnessThrottlingData=" + mBrightnessThrottlingData
+ ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
+ ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
+ ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease
@@ -502,6 +515,7 @@
loadBrightnessDefaultFromDdcXml(config);
loadBrightnessConstraintsFromConfigXml();
loadBrightnessMap(config);
+ loadBrightnessThrottlingMap(config);
loadHighBrightnessModeData(config);
loadQuirks(config);
loadBrightnessRamps(config);
@@ -664,6 +678,41 @@
constrainNitsAndBacklightArrays();
}
+ private void loadBrightnessThrottlingMap(DisplayConfiguration config) {
+ final ThermalThrottling throttlingConfig = config.getThermalThrottling();
+ if (throttlingConfig == null) {
+ Slog.i(TAG, "no thermal throttling config found");
+ return;
+ }
+
+ final BrightnessThrottlingMap map = throttlingConfig.getBrightnessThrottlingMap();
+ if (map == null) {
+ Slog.i(TAG, "no brightness throttling map found");
+ return;
+ }
+
+ final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();
+ // At least 1 point is guaranteed by the display device config schema
+ List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
+ new ArrayList<>(points.size());
+
+ boolean badConfig = false;
+ for (BrightnessThrottlingPoint point : points) {
+ ThermalStatus status = point.getThermalStatus();
+ if (!thermalStatusIsValid(status)) {
+ badConfig = true;
+ break;
+ }
+
+ throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
+ convertThermalStatus(status), point.getBrightness().floatValue()));
+ }
+
+ if (!badConfig) {
+ mBrightnessThrottlingData = BrightnessThrottlingData.create(throttlingLevels);
+ }
+ }
+
private void loadBrightnessMapFromConfigXml() {
// Use the config.xml mapping
final Resources res = mContext.getResources();
@@ -931,6 +980,25 @@
}
}
+ private boolean thermalStatusIsValid(ThermalStatus value) {
+ if (value == null) {
+ return false;
+ }
+
+ switch (value) {
+ case none:
+ case light:
+ case moderate:
+ case severe:
+ case critical:
+ case emergency:
+ case shutdown:
+ return true;
+ default:
+ return false;
+ }
+ }
+
private @PowerManager.ThermalStatus int convertThermalStatus(ThermalStatus value) {
if (value == null) {
return PowerManager.THERMAL_STATUS_NONE;
@@ -1061,4 +1129,91 @@
+ "} ";
}
}
+
+ /**
+ * Container for brightness throttling data.
+ */
+ static class BrightnessThrottlingData {
+ static class ThrottlingLevel {
+ public @PowerManager.ThermalStatus int thermalStatus;
+ public float brightness;
+
+ ThrottlingLevel(@PowerManager.ThermalStatus int thermalStatus, float brightness) {
+ this.thermalStatus = thermalStatus;
+ this.brightness = brightness;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + thermalStatus + "," + brightness + "]";
+ }
+ }
+
+ public List<ThrottlingLevel> throttlingLevels;
+
+ static public BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels)
+ {
+ if (throttlingLevels == null || throttlingLevels.size() == 0) {
+ Slog.e(TAG, "BrightnessThrottlingData received null or empty throttling levels");
+ return null;
+ }
+
+ ThrottlingLevel prevLevel = throttlingLevels.get(0);
+ final int numLevels = throttlingLevels.size();
+ for (int i = 1; i < numLevels; i++) {
+ ThrottlingLevel thisLevel = throttlingLevels.get(i);
+
+ if (thisLevel.thermalStatus <= prevLevel.thermalStatus) {
+ Slog.e(TAG, "brightnessThrottlingMap must be strictly increasing, ignoring "
+ + "configuration. ThermalStatus " + thisLevel.thermalStatus + " <= "
+ + prevLevel.thermalStatus);
+ return null;
+ }
+
+ if (thisLevel.brightness >= prevLevel.brightness) {
+ Slog.e(TAG, "brightnessThrottlingMap must be strictly decreasing, ignoring "
+ + "configuration. Brightness " + thisLevel.brightness + " >= "
+ + thisLevel.brightness);
+ return null;
+ }
+
+ prevLevel = thisLevel;
+ }
+
+ for (ThrottlingLevel level : throttlingLevels) {
+ // Non-negative brightness values are enforced by device config schema
+ if (level.brightness > PowerManager.BRIGHTNESS_MAX) {
+ Slog.e(TAG, "brightnessThrottlingMap contains a brightness value exceeding "
+ + "system max. Brightness " + level.brightness + " > "
+ + PowerManager.BRIGHTNESS_MAX);
+ return null;
+ }
+ }
+
+ return new BrightnessThrottlingData(throttlingLevels);
+ }
+
+ static public BrightnessThrottlingData create(BrightnessThrottlingData other) {
+ if (other == null)
+ return null;
+
+ return BrightnessThrottlingData.create(other.throttlingLevels);
+ }
+
+
+ @Override
+ public String toString() {
+ return "BrightnessThrottlingData{"
+ + "throttlingLevels:" + throttlingLevels
+ + "} ";
+ }
+
+ private BrightnessThrottlingData(List<ThrottlingLevel> inLevels) {
+ throttlingLevels = new ArrayList<>(inLevels.size());
+ for (ThrottlingLevel level : inLevels) {
+ throttlingLevels.add(new ThrottlingLevel(level.thermalStatus, level.brightness));
+ }
+ }
+
+ }
}
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/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index ec4b91a..1f44854 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -346,6 +346,7 @@
private boolean mAppliedTemporaryBrightness;
private boolean mAppliedTemporaryAutoBrightnessAdjustment;
private boolean mAppliedBrightnessBoost;
+ private boolean mAppliedThrottling;
// Reason for which the brightness was last changed. See {@link BrightnessReason} for more
// information.
@@ -379,6 +380,8 @@
private final HighBrightnessModeController mHbmController;
+ private final BrightnessThrottler mBrightnessThrottler;
+
private final BrightnessSetting mBrightnessSetting;
private final Runnable mOnBrightnessChangeRunnable;
@@ -538,6 +541,8 @@
mHbmController = createHbmControllerLocked();
+ mBrightnessThrottler = createBrightnessThrottlerLocked();
+
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
@@ -827,6 +832,8 @@
reloadReduceBrightColours();
mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
mDisplayDeviceConfig.getHighBrightnessModeData());
+ mBrightnessThrottler.resetThrottlingData(
+ mDisplayDeviceConfig.getBrightnessThrottlingData());
}
private void sendUpdatePowerState() {
@@ -1039,6 +1046,7 @@
private void cleanupHandlerThreadAfterStop() {
setProximitySensorEnabled(false);
mHbmController.stop();
+ mBrightnessThrottler.stop();
mHandler.removeCallbacksAndMessages(null);
if (mUnfinishedBusiness) {
mCallbacks.releaseSuspendBlocker();
@@ -1336,14 +1344,6 @@
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
}
- // The current brightness to use has been calculated at this point (minus the adjustments
- // like low-power and dim), and HbmController should be notified so that it can accurately
- // calculate HDR or HBM levels. We specifically do it here instead of having HbmController
- // listen to the brightness setting because certain brightness sources (just as an app
- // override) are not saved to the setting, but should be reflected in HBM
- // calculations.
- mHbmController.onBrightnessChanged(brightnessState);
-
if (updateScreenBrightnessSetting) {
// Tell the rest of the system about the new brightness in case we had to change it
// for things like auto-brightness or high-brightness-mode. Note that we do this
@@ -1390,6 +1390,28 @@
mAppliedLowPower = false;
}
+ // Apply brightness throttling after applying all other transforms
+ final float unthrottledBrightnessState = brightnessState;
+ if (mBrightnessThrottler.isThrottled()) {
+ brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
+ mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED);
+ if (!mAppliedThrottling) {
+ slowChange = false;
+ }
+ mAppliedThrottling = true;
+ } else if (mAppliedThrottling) {
+ slowChange = false;
+ mAppliedThrottling = false;
+ }
+
+ // The current brightness to use has been calculated at this point, and HbmController should
+ // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
+ // here instead of having HbmController listen to the brightness setting because certain
+ // brightness sources (such as an app override) are not saved to the setting, but should be
+ // reflected in HBM calculations.
+ mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
+ mBrightnessThrottler.getBrightnessMaxReason());
+
// Animate the screen brightness when the screen is on or dozing.
// Skip the animation when the screen is off or suspended or transition to/from VR.
boolean brightnessAdjusted = false;
@@ -1441,6 +1463,8 @@
// use instead. We still preserve the calculated brightness for Standard Dynamic Range
// (SDR) layers, but the main brightness value will be the one for HDR.
float sdrAnimateValue = animateValue;
+ // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
+ // done in HighBrightnessModeController.
if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
&& ((mBrightnessReason.modifier & BrightnessReason.MODIFIER_DIMMED) == 0
|| (mBrightnessReason.modifier & BrightnessReason.MODIFIER_LOW_POWER) == 0)) {
@@ -1618,7 +1642,8 @@
mCachedBrightnessInfo.brightnessMin.value,
mCachedBrightnessInfo.brightnessMax.value,
mCachedBrightnessInfo.hbmMode.value,
- mCachedBrightnessInfo.hbmTransitionPoint.value);
+ mCachedBrightnessInfo.hbmTransitionPoint.value,
+ mCachedBrightnessInfo.brightnessMaxReason.value);
}
}
@@ -1648,6 +1673,9 @@
changed |=
mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
mHbmController.getTransitionPoint());
+ changed |=
+ mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
+ mBrightnessThrottler.getBrightnessMaxReason());
return changed;
}
@@ -1679,6 +1707,18 @@
}, mContext);
}
+ private BrightnessThrottler createBrightnessThrottlerLocked() {
+ final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+ final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
+ final DisplayDeviceConfig.BrightnessThrottlingData data =
+ ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
+ return new BrightnessThrottler(mHandler, data,
+ () -> {
+ sendUpdatePowerStateLocked();
+ postBrightnessChangeRunnable();
+ });
+ }
+
private void blockScreenOn() {
if (mPendingScreenOnUnblocker == null) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
@@ -2346,6 +2386,8 @@
pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value);
pw.println(" mCachedBrightnessInfo.hbmTransitionPoint=" +
mCachedBrightnessInfo.hbmTransitionPoint.value);
+ pw.println(" mCachedBrightnessInfo.brightnessMaxReason =" +
+ mCachedBrightnessInfo.brightnessMaxReason .value);
}
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -2384,6 +2426,7 @@
pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness);
pw.println(" mAppliedDimming=" + mAppliedDimming);
pw.println(" mAppliedLowPower=" + mAppliedLowPower);
+ pw.println(" mAppliedThrottling=" + mAppliedThrottling);
pw.println(" mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride);
pw.println(" mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness);
pw.println(" mDozing=" + mDozing);
@@ -2422,6 +2465,10 @@
mHbmController.dump(pw);
}
+ if (mBrightnessThrottler != null) {
+ mBrightnessThrottler.dump(pw);
+ }
+
pw.println();
if (mDisplayWhiteBalanceController != null) {
mDisplayWhiteBalanceController.dump(pw);
@@ -2702,7 +2749,9 @@
static final int MODIFIER_DIMMED = 0x1;
static final int MODIFIER_LOW_POWER = 0x2;
static final int MODIFIER_HDR = 0x4;
- static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR;
+ static final int MODIFIER_THROTTLED = 0x8;
+ static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR
+ | MODIFIER_THROTTLED;
// ADJUSTMENT_*
// These things can happen at any point, even if the main brightness reason doesn't
@@ -2777,6 +2826,9 @@
if ((modifier & MODIFIER_HDR) != 0) {
sb.append(" hdr");
}
+ if ((modifier & MODIFIER_THROTTLED) != 0) {
+ sb.append(" throttled");
+ }
int strlen = sb.length();
if (sb.charAt(strlen - 1) == '[') {
sb.setLength(strlen - 2);
@@ -2813,6 +2865,8 @@
public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
public MutableFloat hbmTransitionPoint =
new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
+ public MutableInt brightnessMaxReason =
+ new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
public boolean checkAndSetFloat(MutableFloat mf, float f) {
if (mf.value != f) {
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index b3be894b..534ed5d 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -82,7 +82,15 @@
private boolean mIsTimeAvailable = false;
private boolean mIsAutoBrightnessEnabled = false;
private boolean mIsAutoBrightnessOffByState = false;
+
+ // The following values are typically reported by DisplayPowerController.
+ // This value includes brightness throttling effects.
private float mBrightness;
+ // This value excludes brightness throttling effects.
+ private float mUnthrottledBrightness;
+ private @BrightnessInfo.BrightnessMaxReason int mThrottlingReason =
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+
private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
private boolean mIsHdrLayerPresent = false;
private boolean mIsThermalStatusWithinLimit = true;
@@ -192,11 +200,14 @@
}
}
- void onBrightnessChanged(float brightness) {
+ void onBrightnessChanged(float brightness, float unthrottledBrightness,
+ @BrightnessInfo.BrightnessMaxReason int throttlingReason) {
if (!deviceSupportsHbm()) {
return;
}
mBrightness = brightness;
+ mUnthrottledBrightness = unthrottledBrightness;
+ mThrottlingReason = throttlingReason;
// If we are starting or ending a high brightness mode session, store the current
// session in mRunningStartTimeMillis, or the old one in mEvents.
@@ -274,6 +285,8 @@
private void dumpLocal(PrintWriter pw) {
pw.println("HighBrightnessModeController:");
pw.println(" mBrightness=" + mBrightness);
+ pw.println(" mUnthrottledBrightness=" + mUnthrottledBrightness);
+ pw.println(" mThrottlingReason=" + BrightnessInfo.briMaxReasonToString(mThrottlingReason));
pw.println(" mCurrentMin=" + getCurrentBrightnessMin());
pw.println(" mCurrentMax=" + getCurrentBrightnessMax());
pw.println(" mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
@@ -431,6 +444,9 @@
+ ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit
+ ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode
+ ", mBrightness: " + mBrightness
+ + ", mUnthrottledBrightness: " + mUnthrottledBrightness
+ + ", mThrottlingReason: "
+ + BrightnessInfo.briMaxReasonToString(mThrottlingReason)
+ ", RunningStartTimeMillis: " + mRunningStartTimeMillis
+ ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1)
+ ", events: " + mEvents);
@@ -446,31 +462,44 @@
private void updateHbmMode() {
int newHbmMode = calculateHighBrightnessMode();
- updateHbmStats(mHbmMode, newHbmMode);
+ updateHbmStats(newHbmMode);
if (mHbmMode != newHbmMode) {
mHbmMode = newHbmMode;
mHbmChangeCallback.run();
}
}
- private void updateHbmStats(int mode, int newMode) {
+ private void updateHbmStats(int newMode) {
+ final float transitionPoint = mHbmData.transitionPoint;
int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
- && getHdrBrightnessValue() > mHbmData.transitionPoint) {
+ && getHdrBrightnessValue() > transitionPoint) {
state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR;
- } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
+ } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT
+ && mBrightness > transitionPoint) {
state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT;
}
if (state == mHbmStatsState) {
return;
}
- mHbmStatsState = state;
int reason =
FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN;
- boolean oldHbmSv = (mode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT);
- boolean newHbmSv = (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+ final boolean oldHbmSv = (mHbmStatsState
+ == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT);
+ final boolean newHbmSv =
+ (state == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT);
if (oldHbmSv && !newHbmSv) {
+ // HighBrightnessModeController (HBMC) currently supports throttling from two sources:
+ // 1. Internal, received from HBMC.SkinThermalStatusObserver.notifyThrottling()
+ // 2. External, received from HBMC.onBrightnessChanged()
+ // TODO(b/216373254): Deprecate internal throttling source
+ final boolean internalThermalThrottling = !mIsThermalStatusWithinLimit;
+ final boolean externalThermalThrottling =
+ mUnthrottledBrightness > transitionPoint && // We would've liked HBM brightness...
+ mBrightness <= transitionPoint && // ...but we got NBM, because of...
+ mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; // ...thermals.
+
// If more than one conditions are flipped and turn off HBM sunlight
// visibility, only one condition will be reported to make it simple.
if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) {
@@ -483,7 +512,7 @@
reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP;
} else if (!mIsTimeAvailable) {
reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT;
- } else if (!mIsThermalStatusWithinLimit) {
+ } else if (internalThermalThrottling || externalThermalThrottling) {
reason = FrameworkStatsLog
.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT;
} else if (mIsHdrLayerPresent) {
@@ -496,6 +525,7 @@
}
mInjector.reportHbmStateChange(mDisplayStatsId, state, reason);
+ mHbmStatsState = state;
}
private String hbmStatsStateToString(int hbmStatsState) {
@@ -572,7 +602,7 @@
>= ((float) (mWidth * mHeight) * HDR_PERCENT_OF_SCREEN_REQUIRED);
// Calling the brightness update so that we can recalculate
// brightness with HDR in mind.
- onBrightnessChanged(mBrightness);
+ onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason);
});
}
}
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/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 9a53d83..80c83e9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -162,28 +162,30 @@
}
@Override
- public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
+ public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
return Collections.emptyList();
}
@Override
- public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
+ public List<InputMethodInfo> getEnabledInputMethodListAsUser(
+ @UserIdInt int userId) {
return Collections.emptyList();
}
@Override
- public void onCreateInlineSuggestionsRequest(int userId,
+ public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
InlineSuggestionsRequestInfo requestInfo,
IInlineSuggestionsRequestCallback cb) {
}
@Override
- public boolean switchToInputMethod(String imeId, int userId) {
+ public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
return false;
}
@Override
- public boolean setInputMethodEnabled(String imeId, boolean enabled, int userId) {
+ public boolean setInputMethodEnabled(String imeId, boolean enabled,
+ @UserIdInt int userId) {
return false;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c6a206b..936b1a2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4977,28 +4977,28 @@
}
@Override
- public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
+ public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
return mService.getInputMethodListAsUser(userId);
}
@Override
- public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
+ public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
return mService.getEnabledInputMethodListAsUser(userId);
}
@Override
- public void onCreateInlineSuggestionsRequest(int userId,
+ public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) {
mService.onCreateInlineSuggestionsRequest(userId, requestInfo, cb);
}
@Override
- public boolean switchToInputMethod(String imeId, int userId) {
+ public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
return mService.switchToInputMethod(imeId, userId);
}
@Override
- public boolean setInputMethodEnabled(String imeId, boolean enabled, int userId) {
+ public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
return mService.setInputMethodEnabled(imeId, enabled, userId);
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index ffef803..aa1fa9b 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1239,29 +1239,29 @@
}
@Override
- @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS)
- public void setAutoGnssSuspended(boolean suspended) {
- mContext.enforceCallingPermission(permission.AUTOMOTIVE_GNSS_CONTROLS, null);
+ @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
+ public void setAutomotiveGnssSuspended(boolean suspended) {
+ mContext.enforceCallingPermission(permission.CONTROL_AUTOMOTIVE_GNSS, null);
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
throw new IllegalStateException(
- "setAutoGnssSuspended only allowed on automotive devices");
+ "setAutomotiveGnssSuspended only allowed on automotive devices");
}
- mGnssManagerService.setAutoGnssSuspended(suspended);
+ mGnssManagerService.setAutomotiveGnssSuspended(suspended);
}
@Override
- @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS)
- public boolean isAutoGnssSuspended() {
- mContext.enforceCallingPermission(permission.AUTOMOTIVE_GNSS_CONTROLS, null);
+ @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
+ public boolean isAutomotiveGnssSuspended() {
+ mContext.enforceCallingPermission(permission.CONTROL_AUTOMOTIVE_GNSS, null);
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
throw new IllegalStateException(
- "isAutoGnssSuspended only allowed on automotive devices");
+ "isAutomotiveGnssSuspended only allowed on automotive devices");
}
- return mGnssManagerService.isAutoGnssSuspended();
+ return mGnssManagerService.isAutomotiveGnssSuspended();
}
@Override
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index c02411e..cd2ba39 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -753,7 +753,7 @@
* Set whether the GnssLocationProvider is suspended. This method was added to help support
* power management use cases on automotive devices.
*/
- public void setAutoGnssSuspended(boolean suspended) {
+ public void setAutomotiveGnssSuspended(boolean suspended) {
synchronized (mLock) {
mAutomotiveSuspend = suspended;
}
@@ -764,7 +764,7 @@
* Return whether the GnssLocationProvider is suspended or not. This method was added to help
* support power management use cases on automotive devices.
*/
- public boolean isAutoGnssSuspended() {
+ public boolean isAutomotiveGnssSuspended() {
synchronized (mLock) {
return mAutomotiveSuspend && !mGpsEnabled;
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index 11fd727..0f9945c 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -113,16 +113,16 @@
* Set whether the GnssLocationProvider is suspended on the device. This method was added to
* help support power management use cases on automotive devices.
*/
- public void setAutoGnssSuspended(boolean suspended) {
- mGnssLocationProvider.setAutoGnssSuspended(suspended);
+ public void setAutomotiveGnssSuspended(boolean suspended) {
+ mGnssLocationProvider.setAutomotiveGnssSuspended(suspended);
}
/**
* Return whether the GnssLocationProvider is suspended or not. This method was added to
* help support power management use cases on automotive devices.
*/
- public boolean isAutoGnssSuspended() {
- return mGnssLocationProvider.isAutoGnssSuspended();
+ public boolean isAutomotiveGnssSuspended() {
+ return mGnssLocationProvider.isAutomotiveGnssSuspended();
}
/** Retrieve the IGpsGeofenceHardware. */
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index fac7a40..8ce67a6 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -98,8 +98,6 @@
import static android.net.NetworkTemplate.MATCH_CARRIER;
import static android.net.NetworkTemplate.MATCH_MOBILE;
import static android.net.NetworkTemplate.MATCH_WIFI;
-import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
-import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
import static android.os.Trace.TRACE_TAG_NETWORK;
import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED;
@@ -168,7 +166,6 @@
import android.net.INetworkManagementEventObserver;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkIdentity;
@@ -1324,7 +1321,7 @@
};
/**
- * Check {@link NetworkPolicy} against current {@link INetworkStatsService}
+ * Check {@link NetworkPolicy} against current {@link NetworkStatsManager}
* to show visible notifications as needed.
*/
@GuardedBy("mNetworkPoliciesSecondLock")
@@ -2322,6 +2319,18 @@
}
/**
+ * Template to match all metered carrier networks with the given IMSI.
+ *
+ * @hide
+ */
+ public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) {
+ Objects.requireNonNull(subscriberId);
+ return new NetworkTemplate.Builder(MATCH_CARRIER)
+ .setSubscriberIds(Set.of(subscriberId))
+ .setMeteredness(METERED_YES).build();
+ }
+
+ /**
* Update the given {@link NetworkPolicy} based on any carrier-provided
* defaults via {@link SubscriptionPlan} or {@link CarrierConfigManager}.
* Leaves policy untouched if the user has modified it.
@@ -2724,7 +2733,8 @@
out.startTag(null, TAG_NETWORK_POLICY);
writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.getMatchRule());
- final String subscriberId = template.getSubscriberId();
+ final String subscriberId = template.getSubscriberIds().isEmpty() ? null
+ : template.getSubscriberIds().iterator().next();
if (subscriberId != null) {
out.attribute(null, ATTR_SUBSCRIBER_ID, subscriberId);
}
@@ -3101,7 +3111,7 @@
}
// When two normalized templates conflict, prefer the most
// restrictive policy
- policy.template = NetworkTemplate.normalize(policy.template, mMergedSubscriberIds);
+ policy.template = normalizeTemplate(policy.template, mMergedSubscriberIds);
final NetworkPolicy existing = mNetworkPolicy.get(policy.template);
if (existing == null || existing.compareTo(policy) > 0) {
if (existing != null) {
@@ -3112,6 +3122,46 @@
}
}
+ /**
+ * Examine the given template and normalize it.
+ * We pick the "lowest" merged subscriber as the primary
+ * for key purposes, and expand the template to match all other merged
+ * subscribers.
+ *
+ * There can be multiple merged subscriberIds for multi-SIM devices.
+ *
+ * <p>
+ * For example, given an incoming template matching B, and the currently
+ * active merge set [A,B], we'd return a new template that primarily matches
+ * A, but also matches B.
+ */
+ private static NetworkTemplate normalizeTemplate(@NonNull NetworkTemplate template,
+ @NonNull List<String[]> mergedList) {
+ // Now there are several types of network which uses Subscriber Id to store network
+ // information. For instance:
+ // 1. A merged carrier wifi network which has TYPE_WIFI with a Subscriber Id.
+ // 2. A typical cellular network could have TYPE_MOBILE with a Subscriber Id.
+
+ if (template.getSubscriberIds().isEmpty()) return template;
+
+ for (final String[] merged : mergedList) {
+ // TODO: Handle incompatible subscriberIds if that happens in practice.
+ for (final String subscriberId : template.getSubscriberIds()) {
+ if (com.android.net.module.util.CollectionUtils.contains(merged, subscriberId)) {
+ // Requested template subscriber is part of the merged group; return
+ // a template that matches all merged subscribers.
+ return new NetworkTemplate.Builder(template.getMatchRule())
+ .setWifiNetworkKeys(template.getWifiNetworkKeys())
+ .setSubscriberIds(Set.of(merged))
+ .setMeteredness(template.getMeteredness())
+ .build();
+ }
+ }
+ }
+
+ return template;
+ }
+
@Override
public void snoozeLimit(NetworkTemplate template) {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
@@ -5559,7 +5609,11 @@
NetworkPolicy[] policies = getNetworkPolicies(mContext.getOpPackageName());
NetworkTemplate templateCarrier = subscriber != null
? buildTemplateCarrierMetered(subscriber) : null;
- NetworkTemplate templateMobile = buildTemplateMobileAll(subscriber);
+ NetworkTemplate templateMobile = subscriber != null
+ ? new NetworkTemplate.Builder(MATCH_MOBILE)
+ .setSubscriberIds(Set.of(subscriber))
+ .setMeteredness(android.net.NetworkStats.METERED_YES)
+ .build() : null;
for (NetworkPolicy policy : policies) {
// All policies loaded from disk will be carrier templates, and setting will also only
// set carrier templates, but we clear mobile templates just in case one is set by
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/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 4b999e9..9f086e6 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -49,6 +49,7 @@
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.SELinuxUtil;
+import com.android.server.pm.pkg.PackageStateInternal;
import dalvik.system.VMRuntime;
@@ -277,8 +278,8 @@
});
}
- public void prepareAppDataContentsLIF(AndroidPackage pkg, @Nullable PackageSetting pkgSetting,
- int userId, int flags) {
+ public void prepareAppDataContentsLIF(AndroidPackage pkg,
+ @Nullable PackageStateInternal pkgSetting, int userId, int flags) {
if (pkg == null) {
Slog.wtf(TAG, "Package was null!", new Throwable());
return;
@@ -287,7 +288,7 @@
}
private void prepareAppDataContentsLeafLIF(AndroidPackage pkg,
- @Nullable PackageSetting pkgSetting, int userId, int flags) {
+ @Nullable PackageStateInternal pkgSetting, int userId, int flags) {
final String volumeUuid = pkg.getVolumeUuid();
final String packageName = pkg.getPackageName();
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index b916de3..5b2e097 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -1237,21 +1237,25 @@
// NOTE: this must come after all removals from data structures but before we update the
// cache
if (setting.getSharedUser() != null) {
- for (int i = setting.getSharedUser().packages.size() - 1; i >= 0; i--) {
- if (setting.getSharedUser().packages.valueAt(i) == setting) {
+ final ArraySet<? extends PackageStateInternal> sharedUserPackages =
+ setting.getSharedUser().getPackageStates();
+ for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
+ if (sharedUserPackages.valueAt(i) == setting) {
continue;
}
addPackageInternal(
- setting.getSharedUser().packages.valueAt(i), settings);
+ sharedUserPackages.valueAt(i), settings);
}
}
synchronized (mCacheLock) {
removeAppIdFromVisibilityCache(setting.getAppId());
if (mShouldFilterCache != null && setting.getSharedUser() != null) {
- for (int i = setting.getSharedUser().packages.size() - 1; i >= 0; i--) {
+ final ArraySet<? extends PackageStateInternal> sharedUserPackages =
+ setting.getSharedUser().getPackageStates();
+ for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
PackageStateInternal siblingSetting =
- setting.getSharedUser().packages.valueAt(i);
+ sharedUserPackages.valueAt(i);
if (siblingSetting == setting) {
continue;
}
@@ -1368,8 +1372,8 @@
callingSharedPkgSettings = null;
} else {
callingPkgSetting = null;
- callingSharedPkgSettings =
- ((PackageStateInternal) callingSetting).getSharedUser().packages;
+ callingSharedPkgSettings = ((PackageStateInternal) callingSetting)
+ .getSharedUser().getPackageStates();
}
} else {
callingPkgSetting = null;
diff --git a/services/core/java/com/android/server/pm/ChangedPackagesTracker.java b/services/core/java/com/android/server/pm/ChangedPackagesTracker.java
new file mode 100644
index 0000000..bd12981
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ChangedPackagesTracker.java
@@ -0,0 +1,114 @@
+/*
+ * 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.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.ChangedPackages;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+class ChangedPackagesTracker {
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ @NonNull
+ private int mChangedPackagesSequenceNumber;
+ /**
+ * List of changed [installed, removed or updated] packages.
+ * mapping from user id -> sequence number -> package name
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private final SparseArray<SparseArray<String>> mUserIdToSequenceToPackage = new SparseArray<>();
+ /**
+ * The sequence number of the last change to a package.
+ * mapping from user id -> package name -> sequence number
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private final SparseArray<Map<String, Integer>> mChangedPackagesSequenceNumbers =
+ new SparseArray<>();
+
+ @Nullable
+ public ChangedPackages getChangedPackages(int sequenceNumber, @UserIdInt int userId) {
+ synchronized (mLock) {
+ if (sequenceNumber >= mChangedPackagesSequenceNumber) {
+ return null;
+ }
+ final SparseArray<String> changedPackages = mUserIdToSequenceToPackage.get(userId);
+ if (changedPackages == null) {
+ return null;
+ }
+ final List<String> packageNames =
+ new ArrayList<>(mChangedPackagesSequenceNumber - sequenceNumber);
+ for (int i = sequenceNumber; i < mChangedPackagesSequenceNumber; i++) {
+ final String packageName = changedPackages.get(i);
+ if (packageName != null) {
+ packageNames.add(packageName);
+ }
+ }
+ return packageNames.isEmpty()
+ ? null : new ChangedPackages(mChangedPackagesSequenceNumber, packageNames);
+ }
+ }
+
+ int getSequenceNumber() {
+ return mChangedPackagesSequenceNumber;
+ }
+
+ void iterateAll(@NonNull BiConsumer<Integer, SparseArray<SparseArray<String>>>
+ sequenceNumberAndValues) {
+ synchronized (mLock) {
+ sequenceNumberAndValues.accept(mChangedPackagesSequenceNumber,
+ mUserIdToSequenceToPackage);
+ }
+ }
+
+ void updateSequenceNumber(@NonNull String packageName, int[] userList) {
+ for (int i = userList.length - 1; i >= 0; --i) {
+ final int userId = userList[i];
+ SparseArray<String> changedPackages = mUserIdToSequenceToPackage.get(userId);
+ if (changedPackages == null) {
+ changedPackages = new SparseArray<>();
+ mUserIdToSequenceToPackage.put(userId, changedPackages);
+ }
+ Map<String, Integer> sequenceNumbers = mChangedPackagesSequenceNumbers.get(userId);
+ if (sequenceNumbers == null) {
+ sequenceNumbers = new HashMap<>();
+ mChangedPackagesSequenceNumbers.put(userId, sequenceNumbers);
+ }
+ final Integer sequenceNumber = sequenceNumbers.get(packageName);
+ if (sequenceNumber != null) {
+ changedPackages.remove(sequenceNumber);
+ }
+ changedPackages.put(mChangedPackagesSequenceNumber, packageName);
+ sequenceNumbers.put(packageName, mChangedPackagesSequenceNumber);
+ }
+ mChangedPackagesSequenceNumber++;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index ba003d2..aa393d2 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -616,98 +616,113 @@
}
void dumpActivityResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
- if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:"
- : "Activity Resolver Table:", " ", packageName,
- dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
- dumpState.setTitlePrinted(true);
+ synchronized (mLock) {
+ if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:"
+ : "Activity Resolver Table:", " ", packageName,
+ dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
+ dumpState.setTitlePrinted(true);
+ }
}
}
void dumpProviderResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
- if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:"
- : "Provider Resolver Table:", " ", packageName,
- dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
- dumpState.setTitlePrinted(true);
+ synchronized (mLock) {
+ if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:"
+ : "Provider Resolver Table:", " ", packageName,
+ dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
+ dumpState.setTitlePrinted(true);
+ }
}
}
void dumpReceiverResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
- if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:"
- : "Receiver Resolver Table:", " ", packageName,
- dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
- dumpState.setTitlePrinted(true);
+ synchronized (mLock) {
+ if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:"
+ : "Receiver Resolver Table:", " ", packageName,
+ dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
+ dumpState.setTitlePrinted(true);
+ }
}
}
void dumpServiceResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
- if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:"
- : "Service Resolver Table:", " ", packageName,
- dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
- dumpState.setTitlePrinted(true);
+ synchronized (mLock) {
+ if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:"
+ : "Service Resolver Table:", " ", packageName,
+ dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) {
+ dumpState.setTitlePrinted(true);
+ }
}
}
void dumpContentProviders(PrintWriter pw, DumpState dumpState, String packageName) {
- boolean printedSomething = false;
- for (ParsedProvider p : mProviders.mProviders.values()) {
- if (packageName != null && !packageName.equals(p.getPackageName())) {
- continue;
- }
- if (!printedSomething) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
+ synchronized (mLock) {
+ boolean printedSomething = false;
+ for (ParsedProvider p : mProviders.mProviders.values()) {
+ if (packageName != null && !packageName.equals(p.getPackageName())) {
+ continue;
}
- pw.println("Registered ContentProviders:");
- printedSomething = true;
- }
- pw.print(" ");
- ComponentName.printShortString(pw, p.getPackageName(), p.getName());
- pw.println(":");
- pw.print(" ");
- pw.println(p.toString());
- }
- printedSomething = false;
- for (Map.Entry<String, ParsedProvider> entry :
- mProvidersByAuthority.entrySet()) {
- ParsedProvider p = entry.getValue();
- if (packageName != null && !packageName.equals(p.getPackageName())) {
- continue;
- }
- if (!printedSomething) {
- if (dumpState.onTitlePrinted()) {
- pw.println();
+ if (!printedSomething) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ pw.println("Registered ContentProviders:");
+ printedSomething = true;
}
- pw.println("ContentProvider Authorities:");
- printedSomething = true;
+ pw.print(" ");
+ ComponentName.printShortString(pw, p.getPackageName(), p.getName());
+ pw.println(":");
+ pw.print(" ");
+ pw.println(p.toString());
}
- pw.print(" ["); pw.print(entry.getKey()); pw.println("]:");
- pw.print(" "); pw.println(p.toString());
+ printedSomething = false;
+ for (Map.Entry<String, ParsedProvider> entry :
+ mProvidersByAuthority.entrySet()) {
+ ParsedProvider p = entry.getValue();
+ if (packageName != null && !packageName.equals(p.getPackageName())) {
+ continue;
+ }
+ if (!printedSomething) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ pw.println("ContentProvider Authorities:");
+ printedSomething = true;
+ }
+ pw.print(" [");
+ pw.print(entry.getKey());
+ pw.println("]:");
+ pw.print(" ");
+ pw.println(p.toString());
- AndroidPackage pkg = sPackageManagerInternal.getPackage(p.getPackageName());
+ AndroidPackage pkg = sPackageManagerInternal.getPackage(p.getPackageName());
- if (pkg != null) {
- pw.print(" applicationInfo=");
- pw.println(AndroidPackageUtils.generateAppInfoWithoutState(pkg));
+ if (pkg != null) {
+ pw.print(" applicationInfo=");
+ pw.println(AndroidPackageUtils.generateAppInfoWithoutState(pkg));
+ }
}
}
}
void dumpServicePermissions(PrintWriter pw, DumpState dumpState) {
- if (dumpState.onTitlePrinted()) pw.println();
- pw.println("Service permissions:");
+ synchronized (mLock) {
+ if (dumpState.onTitlePrinted()) pw.println();
+ pw.println("Service permissions:");
- final Iterator<Pair<ParsedService, ParsedIntentInfo>> filterIterator =
- mServices.filterIterator();
- while (filterIterator.hasNext()) {
- final Pair<ParsedService, ParsedIntentInfo> pair = filterIterator.next();
- ParsedService service = pair.first;
+ final Iterator<Pair<ParsedService, ParsedIntentInfo>> filterIterator =
+ mServices.filterIterator();
+ while (filterIterator.hasNext()) {
+ final Pair<ParsedService, ParsedIntentInfo> pair = filterIterator.next();
+ ParsedService service = pair.first;
- final String permission = service.getPermission();
- if (permission != null) {
- pw.print(" ");
- pw.print(service.getComponentName().flattenToShortString());
- pw.print(": ");
- pw.println(permission);
+ final String permission = service.getPermission();
+ if (permission != null) {
+ pw.print(" ");
+ pw.print(service.getComponentName().flattenToShortString());
+ pw.print(": ");
+ pw.println(permission);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index fcf4a02..0ae3418 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -43,12 +43,16 @@
import android.content.pm.VersionedPackage;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
+import com.android.server.utils.WatchedArrayMap;
+import com.android.server.utils.WatchedLongSparseArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -214,7 +218,7 @@
@Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED)
String resolveExternalPackageName(AndroidPackage pkg);
@Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED)
- String resolveInternalPackageNameLPr(String packageName, long versionCode);
+ String resolveInternalPackageName(String packageName, long versionCode);
@Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED)
String[] getPackagesForUid(int uid);
@Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED)
@@ -381,11 +385,12 @@
String[] getSystemSharedLibraryNames();
/**
- * @return if the given package has a state and isn't filtered by visibility. Provides no
- * guarantee that the package is in any usable state.
+ * @return the state if the given package has a state and isn't filtered by visibility.
+ * Provides no guarantee that the package is in any usable state.
*/
@Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
- boolean isPackageStateAvailableAndVisible(@NonNull String packageName, int callingUid,
+ @Nullable
+ PackageStateInternal getPackageStateFiltered(@NonNull String packageName, int callingUid,
@UserIdInt int userId);
@Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
@@ -623,4 +628,16 @@
@Nullable
ArrayMap<String, ProcessInfo> getProcessesForUid(int uid);
// End block
+
+ @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
+ boolean getBlockUninstall(@UserIdInt int userId, @NonNull String packageName);
+
+ @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
+ @NonNull
+ WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries();
+
+
+ @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
+ @Nullable
+ Pair<PackageStateInternal, SharedUserApi> getPackageOrSharedUser(int appId);
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index cca1b97..691bf9f 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -133,9 +133,9 @@
import com.android.server.pm.pkg.PackageStateImpl;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUtils;
-import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.PackageUserStateUtils;
+import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.pm.pkg.component.ParsedActivity;
import com.android.server.pm.pkg.component.ParsedInstrumentation;
import com.android.server.pm.pkg.component.ParsedIntentInfo;
@@ -298,11 +298,6 @@
public Collection<SharedUserSetting> getAllSharedUsers() {
return mSettings.getAllSharedUsersLPw();
}
-
- @Nullable
- public String getHarmfulAppWarning(@NonNull String packageName, @UserIdInt int userId) {
- return mSettings.getHarmfulAppWarningLPr(packageName, userId);
- }
}
private static final Comparator<ProviderInfo> sProviderInitOrderSorter = (p1, p2) -> {
@@ -829,7 +824,7 @@
}
public AndroidPackage getPackage(String packageName) {
- packageName = resolveInternalPackageNameLPr(
+ packageName = resolveInternalPackageName(
packageName, PackageManager.VERSION_CODE_HIGHEST);
return mPackages.get(packageName);
}
@@ -903,7 +898,7 @@
int filterCallingUid, int userId) {
// writer
// Normalize package name to handle renamed packages and static libs
- packageName = resolveInternalPackageNameLPr(packageName,
+ packageName = resolveInternalPackageName(packageName,
PackageManager.VERSION_CODE_HIGHEST);
AndroidPackage p = mPackages.get(packageName);
@@ -1575,7 +1570,7 @@
PackageInfo pi = new PackageInfo();
pi.packageName = ps.getPackageName();
pi.setLongVersionCode(ps.getVersionCode());
- pi.sharedUserId = (ps.getSharedUser() != null) ? ps.getSharedUser().name : null;
+ pi.sharedUserId = (ps.getSharedUser() != null) ? ps.getSharedUser().getName() : null;
pi.firstInstallTime = state.getFirstInstallTime();
pi.lastUpdateTime = ps.getLastUpdateTime();
@@ -1627,7 +1622,7 @@
long flags, int filterCallingUid, int userId) {
// reader
// Normalize package name to handle renamed packages and static libs
- packageName = resolveInternalPackageNameLPr(packageName, versionCode);
+ packageName = resolveInternalPackageName(packageName, versionCode);
final boolean matchFactoryOnly = (flags & MATCH_FACTORY_ONLY) != 0;
if (matchFactoryOnly) {
@@ -2111,7 +2106,7 @@
return packageName;
}
- public final String resolveInternalPackageNameLPr(String packageName, long versionCode) {
+ public final String resolveInternalPackageName(String packageName, long versionCode) {
final int callingUid = Binder.getCallingUid();
return resolveInternalPackageNameInternalLocked(packageName, versionCode,
callingUid);
@@ -3489,7 +3484,9 @@
return mSettings.getRenamedPackageLPr(packageName);
}
- private WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
+ @NonNull
+ @Override
+ public WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
getSharedLibraries() {
return mSharedLibraries.getAll();
}
@@ -4070,10 +4067,13 @@
}
@Override
- public boolean isPackageStateAvailableAndVisible(@NonNull String packageName, int callingUid,
+ public PackageStateInternal getPackageStateFiltered(@NonNull String packageName, int callingUid,
@UserIdInt int userId) {
- final PackageStateInternal ps = getPackageStateInternal(packageName);
- return ps != null && !shouldFilterApplication(ps, callingUid, userId);
+ final PackageStateInternal packageState = getPackageStateInternal(packageName);
+ if (packageState == null || shouldFilterApplication(packageState, callingUid, userId)) {
+ return null;
+ }
+ return packageState;
}
@Override
@@ -4576,9 +4576,9 @@
}
} else {
list = new ArrayList<>(mPackages.size());
- for (AndroidPackage p : mPackages.values()) {
- final PackageStateInternal packageState = packageStates.get(p.getPackageName());
- if (packageState == null) {
+ for (PackageStateInternal packageState : packageStates.values()) {
+ final AndroidPackage pkg = packageState.getPkg();
+ if (pkg == null) {
continue;
}
if (filterSharedLibPackage(packageState, Binder.getCallingUid(), userId, flags)) {
@@ -4587,10 +4587,10 @@
if (shouldFilterApplication(packageState, callingUid, userId)) {
continue;
}
- ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(p, flags,
+ ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(pkg, flags,
packageState.getUserStateOrDefault(userId), userId, packageState);
if (ai != null) {
- ai.packageName = resolveExternalPackageName(p);
+ ai.packageName = resolveExternalPackageName(pkg);
list.add(ai);
}
}
@@ -5421,13 +5421,14 @@
return EmptyArray.STRING;
}
- ArraySet<PackageSetting> packages = packageSetting.getSharedUser().packages;
+ ArraySet<? extends PackageStateInternal> packages =
+ packageSetting.getSharedUser().getPackageStates();
final int numPackages = packages.size();
String[] res = new String[numPackages];
int i = 0;
for (int index = 0; index < numPackages; index++) {
- final PackageSetting ps = packages.valueAt(index);
- if (ps.getInstalled(userId)) {
+ final PackageStateInternal ps = packages.valueAt(index);
+ if (ps.getUserStateOrDefault(userId).isInstalled()) {
res[i++] = ps.getPackageName();
}
}
@@ -5476,7 +5477,11 @@
+ SET_HARMFUL_APP_WARNINGS + " permission.");
}
- return mSettings.getHarmfulAppWarning(packageName, userId);
+ final PackageStateInternal packageState = getPackageStateInternal(packageName);
+ if (packageState == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ return packageState.getUserStateOrDefault(userId).getHarmfulAppWarning();
}
/**
@@ -5571,4 +5576,22 @@
}
return null;
}
+
+ @Override
+ public boolean getBlockUninstall(@UserIdInt int userId, @NonNull String packageName) {
+ return mSettings.getBlockUninstall(userId, packageName);
+ }
+
+ @Nullable
+ @Override
+ public Pair<PackageStateInternal, SharedUserApi> getPackageOrSharedUser(int appId) {
+ final SettingBase settingBase = mSettings.getSettingBase(appId);
+ if (settingBase instanceof SharedUserSetting) {
+ return Pair.create(null, (SharedUserApi) settingBase);
+ } else if (settingBase instanceof PackageSetting) {
+ return Pair.create((PackageStateInternal) settingBase, null);
+ } else {
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/ComputerLocked.java b/services/core/java/com/android/server/pm/ComputerLocked.java
index 529aca3..40d4c03 100644
--- a/services/core/java/com/android/server/pm/ComputerLocked.java
+++ b/services/core/java/com/android/server/pm/ComputerLocked.java
@@ -40,12 +40,16 @@
import android.content.pm.VersionedPackage;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
+import com.android.server.utils.WatchedArrayMap;
+import com.android.server.utils.WatchedLongSparseArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -390,14 +394,6 @@
}
@Override
- public boolean isPackageStateAvailableAndVisible(@NonNull String packageName, int callingUid,
- @UserIdInt int userId) {
- synchronized (mLock) {
- return super.isPackageStateAvailableAndVisible(packageName, callingUid, userId);
- }
- }
-
- @Override
public int checkSignatures(@NonNull String pkg1,
@NonNull String pkg2) {
synchronized (mLock) {
@@ -825,4 +821,35 @@
return super.getProcessesForUid(uid);
}
}
+
+ @Override
+ public PackageStateInternal getPackageStateFiltered(@NonNull String packageName, int callingUid,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ return super.getPackageStateFiltered(packageName, callingUid, userId);
+ }
+ }
+
+ @Override
+ public boolean getBlockUninstall(@UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mLock) {
+ return super.getBlockUninstall(userId, packageName);
+ }
+ }
+
+ @NonNull
+ @Override
+ public WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries() {
+ synchronized (mLock) {
+ return super.getSharedLibraries();
+ }
+ }
+
+ @Nullable
+ @Override
+ public Pair<PackageStateInternal, SharedUserApi> getPackageOrSharedUser(int appId) {
+ synchronized (mLock) {
+ return super.getPackageOrSharedUser(appId);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/ComputerTracker.java b/services/core/java/com/android/server/pm/ComputerTracker.java
index 52309ce..24c08d1 100644
--- a/services/core/java/com/android/server/pm/ComputerTracker.java
+++ b/services/core/java/com/android/server/pm/ComputerTracker.java
@@ -42,11 +42,15 @@
import android.content.pm.VersionedPackage;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
+import com.android.server.utils.WatchedArrayMap;
+import com.android.server.utils.WatchedLongSparseArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -63,26 +67,11 @@
// a snapshot computer.
private final AtomicInteger mReusedSnapshot = new AtomicInteger(0);
- // The number of times a thread reused a computer in its stack instead of fetching
- // a live computer.
- private final AtomicInteger mReusedLive = new AtomicInteger(0);
-
private final PackageManagerService mService;
ComputerTracker(PackageManagerService s) {
mService = s;
}
- private ThreadComputer live() {
- ThreadComputer current = PackageManagerService.sThreadComputer.get();
- if (current.mRefCount > 0) {
- current.acquire();
- mReusedLive.incrementAndGet();
- } else {
- current.acquire(mService.liveComputer());
- }
- return current;
- }
-
private ThreadComputer snapshot() {
ThreadComputer current = PackageManagerService.sThreadComputer.get();
if (current.mRefCount > 0) {
@@ -133,7 +122,7 @@
Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits,
String pkgName, String instantAppPkgName) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.queryIntentActivitiesInternalBody(intent, resolvedType,
flags, filterCallingUid, userId, resolveForStart, allowDynamicSplits,
@@ -154,7 +143,7 @@
public ActivityInfo getActivityInfoInternal(ComponentName component,
@PackageManager.ComponentInfoFlagsBits long flags,
int filterCallingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getActivityInfoInternal(component, flags, filterCallingUid,
userId);
@@ -180,7 +169,7 @@
}
public ApplicationInfo generateApplicationInfoFromSettings(String packageName,
long flags, int filterCallingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.generateApplicationInfoFromSettings(packageName, flags,
filterCallingUid, userId);
@@ -199,7 +188,7 @@
}
public ApplicationInfo getApplicationInfoInternal(String packageName,
@PackageManager.ApplicationInfoFlagsBits long flags, int filterCallingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getApplicationInfoInternal(packageName, flags,
filterCallingUid, userId);
@@ -208,7 +197,7 @@
}
}
public ComponentName getDefaultHomeActivity(int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getDefaultHomeActivity(userId);
} finally {
@@ -217,7 +206,7 @@
}
public ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getHomeActivitiesAsUser(allHomeCandidates, userId);
} finally {
@@ -227,7 +216,7 @@
public CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int sourceUserId,
int parentUserId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getCrossProfileDomainPreferredLpr(intent, resolvedType,
flags, sourceUserId, parentUserId);
@@ -236,7 +225,7 @@
}
}
public Intent getHomeIntent() {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getHomeIntent();
} finally {
@@ -245,7 +234,7 @@
}
public List<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(
Intent intent, String resolvedType, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getMatchingCrossProfileIntentFilters(intent, resolvedType,
userId);
@@ -257,7 +246,7 @@
@NonNull List<ResolveInfo> resolveInfos,
String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid,
boolean resolveForStart, int userId, Intent intent) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.applyPostResolutionFilter(resolveInfos, ephemeralPkgName,
allowDynamicSplits, filterCallingUid, resolveForStart, userId, intent);
@@ -267,7 +256,7 @@
}
public PackageInfo generatePackageInfo(PackageStateInternal ps,
@PackageManager.PackageInfoFlagsBits long flags, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.generatePackageInfo(ps, flags, userId);
} finally {
@@ -285,7 +274,7 @@
}
public PackageInfo getPackageInfoInternal(String packageName, long versionCode,
long flags, int filterCallingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getPackageInfoInternal(packageName, versionCode, flags,
filterCallingUid, userId);
@@ -302,7 +291,7 @@
}
}
public PackageStateInternal getPackageStateInternal(String packageName, int callingUid) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getPackageStateInternal(packageName, callingUid);
} finally {
@@ -312,7 +301,7 @@
@Nullable
public PackageState getPackageStateCopied(@NonNull String packageName) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getPackageStateCopied(packageName);
} finally {
@@ -330,7 +319,7 @@
}
public ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter,
int sourceUserId, int targetUserId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.createForwardingResolveInfoUnchecked(filter, sourceUserId,
targetUserId);
@@ -340,7 +329,7 @@
}
public ServiceInfo getServiceInfo(ComponentName component,
@PackageManager.ComponentInfoFlagsBits long flags, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getServiceInfo(component, flags, userId);
} finally {
@@ -380,17 +369,17 @@
}
}
public String resolveExternalPackageName(AndroidPackage pkg) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.resolveExternalPackageName(pkg);
} finally {
current.release();
}
}
- public String resolveInternalPackageNameLPr(String packageName, long versionCode) {
- ThreadComputer current = live();
+ public String resolveInternalPackageName(String packageName, long versionCode) {
+ ThreadComputer current = snapshot();
try {
- return current.mComputer.resolveInternalPackageNameLPr(packageName, versionCode);
+ return current.mComputer.resolveInternalPackageName(packageName, versionCode);
} finally {
current.release();
}
@@ -404,7 +393,7 @@
}
}
public UserInfo getProfileParent(int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getProfileParent(userId);
} finally {
@@ -412,7 +401,7 @@
}
}
public boolean canViewInstantApps(int callingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.canViewInstantApps(callingUid, userId);
} finally {
@@ -445,7 +434,7 @@
}
public boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid,
int userId, @PackageManager.ComponentInfoFlagsBits long flags) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.filterSharedLibPackage(ps, uid, userId, flags);
} finally {
@@ -453,7 +442,7 @@
}
}
public boolean isCallerSameApp(String packageName, int uid) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.isCallerSameApp(packageName, uid);
} finally {
@@ -461,7 +450,7 @@
}
}
public boolean isComponentVisibleToInstantApp(@Nullable ComponentName component) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.isComponentVisibleToInstantApp(component);
} finally {
@@ -470,7 +459,7 @@
}
public boolean isComponentVisibleToInstantApp(@Nullable ComponentName component,
@PackageManager.ComponentType int type) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.isComponentVisibleToInstantApp(component, type);
} finally {
@@ -479,7 +468,7 @@
}
public boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent,
int userId, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent,
userId, resolvedType, flags);
@@ -497,7 +486,7 @@
}
public boolean isInstantAppInternal(String packageName, @UserIdInt int userId,
int callingUid) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.isInstantAppInternal(packageName, userId, callingUid);
} finally {
@@ -506,7 +495,7 @@
}
public boolean isSameProfileGroup(@UserIdInt int callerUserId,
@UserIdInt int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.isSameProfileGroup(callerUserId, userId);
} finally {
@@ -515,7 +504,7 @@
}
public boolean shouldFilterApplication(@NonNull SharedUserSetting sus,
int callingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.shouldFilterApplication(sus, callingUid, userId);
} finally {
@@ -525,7 +514,7 @@
public boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
int callingUid, @Nullable ComponentName component,
@PackageManager.ComponentType int componentType, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.shouldFilterApplication(ps, callingUid, component,
componentType, userId);
@@ -535,7 +524,7 @@
}
public boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
int callingUid, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.shouldFilterApplication(ps, callingUid, userId);
} finally {
@@ -552,7 +541,7 @@
}
public int getPackageUidInternal(String packageName,
@PackageManager.PackageInfoFlagsBits long flags, int userId, int callingUid) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.getPackageUidInternal(packageName, flags, userId,
callingUid);
@@ -561,7 +550,7 @@
}
}
public long updateFlagsForApplication(long flags, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.updateFlagsForApplication(flags, userId);
} finally {
@@ -569,7 +558,7 @@
}
}
public long updateFlagsForComponent(long flags, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.updateFlagsForComponent(flags, userId);
} finally {
@@ -577,7 +566,7 @@
}
}
public long updateFlagsForPackage(long flags, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.updateFlagsForPackage(flags, userId);
} finally {
@@ -597,7 +586,7 @@
public long updateFlagsForResolve(long flags, int userId, int callingUid,
boolean wantInstantApps, boolean onlyExposedExplicitly,
boolean isImplicitImageCaptureIntentAndNotSetByDpc) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.updateFlagsForResolve(flags, userId, callingUid,
wantInstantApps, onlyExposedExplicitly,
@@ -607,7 +596,7 @@
}
}
public void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
current.mComputer.dump(type, fd, pw, dumpState);
} finally {
@@ -616,7 +605,7 @@
}
public void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId,
boolean requireFullPermission, boolean checkShell, String message) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
current.mComputer.enforceCrossUserOrProfilePermission(callingUid, userId,
requireFullPermission, checkShell, message);
@@ -626,7 +615,7 @@
}
public void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
boolean requireFullPermission, boolean checkShell, String message) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
current.mComputer.enforceCrossUserPermission(callingUid, userId,
requireFullPermission, checkShell, message);
@@ -637,7 +626,7 @@
public void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
boolean requireFullPermission, boolean checkShell,
boolean requirePermissionWhenSameUser, String message) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
current.mComputer.enforceCrossUserPermission(callingUid, userId,
requireFullPermission, checkShell, requirePermissionWhenSameUser, message);
@@ -649,7 +638,7 @@
Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
List<ResolveInfo> query, boolean always, boolean removeMatches, boolean debug,
int userId, boolean queryMayBeFiltered) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.findPreferredActivityInternal(intent, resolvedType, flags,
query, always, removeMatches, debug, userId, queryMayBeFiltered);
@@ -660,7 +649,7 @@
public ResolveInfo findPersistentPreferredActivityLP(Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
List<ResolveInfo> query, boolean debug, int userId) {
- ThreadComputer current = live();
+ ThreadComputer current = snapshot();
try {
return current.mComputer.findPersistentPreferredActivityLP(intent, resolvedType,
flags, query, debug, userId);
@@ -834,15 +823,6 @@
}
@Override
- public boolean isPackageStateAvailableAndVisible(@NonNull String packageName, int callingUid,
- @UserIdInt int userId) {
- try (ThreadComputer current = snapshot()) {
- return current.mComputer.isPackageStateAvailableAndVisible(packageName, callingUid,
- userId);
- }
- }
-
- @Override
public int checkSignatures(@NonNull String pkg1,
@NonNull String pkg2) {
try (ThreadComputer current = snapshot()) {
@@ -1272,4 +1252,35 @@
return current.mComputer.getProcessesForUid(uid);
}
}
+
+ @Override
+ public PackageStateInternal getPackageStateFiltered(@NonNull String packageName, int callingUid,
+ @UserIdInt int userId) {
+ try (ThreadComputer current = snapshot()) {
+ return current.mComputer.getPackageStateFiltered(packageName, callingUid, userId);
+ }
+ }
+
+ @Override
+ public boolean getBlockUninstall(@UserIdInt int userId, @NonNull String packageName) {
+ try (ThreadComputer current = snapshot()) {
+ return current.mComputer.getBlockUninstall(userId, packageName);
+ }
+ }
+
+ @NonNull
+ @Override
+ public WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries() {
+ try (ThreadComputer current = snapshot()) {
+ return current.mComputer.getSharedLibraries();
+ }
+ }
+
+ @Nullable
+ @Override
+ public Pair<PackageStateInternal, SharedUserApi> getPackageOrSharedUser(int appId) {
+ try (ThreadComputer current = snapshot()) {
+ return current.mComputer.getPackageOrSharedUser(appId);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 48689a8..bd36b472 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -62,6 +62,7 @@
import com.android.internal.util.Preconditions;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -220,12 +221,12 @@
res = deletePackageLIF(packageName, UserHandle.of(removeUser), true, allUsers,
deleteFlags | PackageManager.DELETE_CHATTY, info, true);
}
+ if (res && pkg != null) {
+ mPm.mInstantAppRegistry.onPackageUninstalled(pkg, uninstalledPs,
+ info.mRemovedUsers);
+ }
synchronized (mPm.mLock) {
if (res) {
- if (pkg != null) {
- mPm.mInstantAppRegistry.onPackageUninstalledLPw(pkg, uninstalledPs,
- info.mRemovedUsers);
- }
mPm.updateSequenceNumberLP(uninstalledPs, info.mRemovedUsers);
mPm.updateInstantAppInstallerLocked(packageName);
}
@@ -421,9 +422,7 @@
}
if (clearPackageStateAndReturn) {
clearPackageStateForUserLIF(ps, userId, outInfo, flags);
- synchronized (mPm.mLock) {
- mPm.scheduleWritePackageRestrictionsLocked(user);
- }
+ mPm.scheduleWritePackageRestrictions(user);
return;
}
}
@@ -622,7 +621,6 @@
final String packageName = versionedPackage.getPackageName();
final long versionCode = versionedPackage.getLongVersionCode();
- final String internalPackageName;
try {
if (mPm.mInjector.getLocalService(ActivityTaskManagerInternal.class)
@@ -636,10 +634,8 @@
e.rethrowFromSystemServer();
}
- synchronized (mPm.mLock) {
- // Normalize package name to handle renamed packages and static libs
- internalPackageName = mPm.resolveInternalPackageNameLPr(packageName, versionCode);
- }
+ // Normalize package name to handle renamed packages and static libs
+ final String internalPackageName = mPm.resolveInternalPackageName(packageName, versionCode);
final int uid = Binder.getCallingUid();
if (!isOrphaned(internalPackageName)
@@ -748,13 +744,8 @@
}
private boolean isOrphaned(String packageName) {
- // reader
- synchronized (mPm.mLock) {
- if (!mPm.mPackages.containsKey(packageName)) {
- return false;
- }
- return mPm.mSettings.isOrphaned(packageName);
- }
+ final PackageStateInternal packageState = mPm.getPackageStateInternal(packageName);
+ return packageState != null && packageState.getInstallSource().isOrphaned;
}
private boolean isCallerAllowedToSilentlyUninstall(int callingUid, String pkgName) {
@@ -894,7 +885,7 @@
int installedForUsersCount = 0;
synchronized (mPm.mLock) {
// Normalize package name to handle renamed packages and static libs
- final String internalPkgName = mPm.resolveInternalPackageNameLPr(packageName,
+ final String internalPkgName = mPm.resolveInternalPackageName(packageName,
versionCode);
final PackageSetting ps = mPm.mSettings.getPackageLPr(internalPkgName);
if (ps != null) {
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index dcad3ec..ba89916 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -71,10 +71,15 @@
private final PackageManagerService mPm;
public boolean isDexOptDialogShown() {
- return mDexOptDialogShown;
+ synchronized (mLock) {
+ return mDexOptDialogShown;
+ }
}
- @GuardedBy("mPm.mLock")
+ // TODO: Is this lock really necessary?
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
private boolean mDexOptDialogShown;
DexOptHelper(PackageManagerService pm) {
@@ -191,7 +196,7 @@
numberOfPackagesVisited, numberOfPackagesToDexopt), true);
} catch (RemoteException e) {
}
- synchronized (mPm.mLock) {
+ synchronized (mLock) {
mDexOptDialogShown = true;
}
}
@@ -287,13 +292,11 @@
public ArraySet<String> getOptimizablePackages() {
ArraySet<String> pkgs = new ArraySet<>();
- synchronized (mPm.mLock) {
- for (AndroidPackage p : mPm.mPackages.values()) {
- if (mPm.mPackageDexOptimizer.canOptimizePackage(p)) {
- pkgs.add(p.getPackageName());
- }
+ mPm.forEachPackageState(packageState -> {
+ if (mPm.mPackageDexOptimizer.canOptimizePackage(packageState.getPkg())) {
+ pkgs.add(packageState.getPackageName());
}
- }
+ });
return pkgs;
}
@@ -415,14 +418,10 @@
public void forceDexOpt(String packageName) {
PackageManagerServiceUtils.enforceSystemOrRoot("forceDexOpt");
- AndroidPackage pkg;
- PackageSetting pkgSetting;
- synchronized (mPm.mLock) {
- pkg = mPm.mPackages.get(packageName);
- pkgSetting = mPm.mSettings.getPackageLPr(packageName);
- if (pkg == null || pkgSetting == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
+ final PackageStateInternal packageState = mPm.getPackageStateInternal(packageName);
+ final AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
+ if (packageState == null || pkg == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
}
synchronized (mPm.mInstallLock) {
@@ -430,7 +429,7 @@
// Whoever is calling forceDexOpt wants a compiled package.
// Don't use profiles since that may cause compilation to be skipped.
- final int res = performDexOptInternalWithDependenciesLI(pkg, pkgSetting,
+ final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
new DexoptOptions(packageName,
getDefaultCompilerFilter(),
DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index 55d1293..5ab0c4c 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -39,6 +39,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.function.BiConsumer;
/**
* Dumps PackageManagerService internal states.
@@ -117,7 +118,7 @@
}
// Normalize package name to handle renamed packages and static libs
- pkg = mPm.resolveInternalPackageNameLPr(pkg, PackageManager.VERSION_CODE_HIGHEST);
+ pkg = mPm.resolveInternalPackageName(pkg, PackageManager.VERSION_CODE_HIGHEST);
pw.println(mPm.checkPermission(perm, pkg, user));
return;
@@ -369,33 +370,20 @@
}
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) {
- synchronized (mPm.mLock) {
- mPm.mComponentResolver.dumpActivityResolvers(pw, dumpState, packageName);
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) {
+ mPm.mComponentResolver.dumpActivityResolvers(pw, dumpState, packageName);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) {
- synchronized (mPm.mLock) {
- mPm.mComponentResolver.dumpReceiverResolvers(pw, dumpState, packageName);
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) {
+ mPm.mComponentResolver.dumpReceiverResolvers(pw, dumpState, packageName);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) {
- synchronized (mPm.mLock) {
- mPm.mComponentResolver.dumpServiceResolvers(pw, dumpState, packageName);
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) {
+ mPm.mComponentResolver.dumpServiceResolvers(pw, dumpState, packageName);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) {
- synchronized (mPm.mLock) {
- mPm.mComponentResolver.dumpProviderResolvers(pw, dumpState, packageName);
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) {
+ mPm.mComponentResolver.dumpProviderResolvers(pw, dumpState, packageName);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
mPm.dumpComputer(DumpState.DUMP_PREFERRED, fd, pw, dumpState);
}
@@ -405,23 +393,16 @@
mPm.dumpComputer(DumpState.DUMP_PREFERRED_XML, fd, pw, dumpState);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
mPm.dumpComputer(DumpState.DUMP_DOMAIN_PREFERRED, fd, pw, dumpState);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
- synchronized (mPm.mLock) {
- mPm.mSettings.dumpPermissions(pw, packageName, permissionNames, dumpState);
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
+ mPm.mSettings.dumpPermissions(pw, packageName, permissionNames, dumpState);
}
- if (!checkin
- && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
- synchronized (mPm.mLock) {
- mPm.mComponentResolver.dumpContentProviders(pw, dumpState, packageName);
- }
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
+ mPm.mComponentResolver.dumpContentProviders(pw, dumpState, packageName);
}
if (!checkin
@@ -461,28 +442,28 @@
pw.println();
}
pw.println("Package Changes:");
- synchronized (mPm.mLock) {
- pw.print(" Sequence number="); pw.println(mPm.mChangedPackagesSequenceNumber);
- final int numChangedPackages = mPm.mChangedPackages.size();
+ mPm.mChangedPackagesTracker.iterateAll((sequenceNumber, values) -> {
+ pw.print(" Sequence number="); pw.println(sequenceNumber);
+ final int numChangedPackages = values.size();
for (int i = 0; i < numChangedPackages; i++) {
- final SparseArray<String> changes = mPm.mChangedPackages.valueAt(i);
- pw.print(" User "); pw.print(mPm.mChangedPackages.keyAt(i)); pw.println(":");
+ final SparseArray<String> changes = values.valueAt(i);
+ pw.print(" User "); pw.print(values.keyAt(i)); pw.println(":");
final int numChanges = changes.size();
if (numChanges == 0) {
pw.print(" "); pw.println("No packages changed");
} else {
for (int j = 0; j < numChanges; j++) {
final String pkgName = changes.valueAt(j);
- final int sequenceNumber = changes.keyAt(j);
+ final int userSequenceNumber = changes.keyAt(j);
pw.print(" ");
pw.print("seq=");
- pw.print(sequenceNumber);
+ pw.print(userSequenceNumber);
pw.print(", package=");
pw.println(pkgName);
}
}
}
- }
+ });
}
if (!checkin
@@ -537,9 +518,7 @@
if (!checkin
&& dumpState.isDumping(DumpState.DUMP_SERVICE_PERMISSIONS)
&& packageName == null) {
- synchronized (mPm.mLock) {
- mPm.mComponentResolver.dumpServicePermissions(pw, dumpState);
- }
+ mPm.mComponentResolver.dumpServicePermissions(pw, dumpState);
}
if (!checkin
@@ -558,9 +537,7 @@
if (dumpState.onTitlePrinted()) {
pw.println();
}
- synchronized (mPm.mLock) {
- mPm.mSettings.dumpReadMessagesLPr(pw, dumpState);
- }
+ mPm.mSettings.dumpReadMessages(pw, dumpState);
pw.println();
pw.println("Package warning messages:");
dumpCriticalInfo(pw, null);
diff --git a/services/core/java/com/android/server/pm/IncrementalProgressListener.java b/services/core/java/com/android/server/pm/IncrementalProgressListener.java
index 018501d..fa11924 100644
--- a/services/core/java/com/android/server/pm/IncrementalProgressListener.java
+++ b/services/core/java/com/android/server/pm/IncrementalProgressListener.java
@@ -18,6 +18,8 @@
import android.content.pm.IPackageLoadingProgressCallback;
+import com.android.server.pm.pkg.PackageStateInternal;
+
/**
* Loading progress callback, used to listen for progress changes and update package setting
*/
@@ -31,25 +33,24 @@
@Override
public void onPackageLoadingProgressChanged(float progress) {
- final PackageSetting ps;
- synchronized (mPm.mLock) {
- ps = mPm.mSettings.getPackageLPr(mPackageName);
- if (ps == null) {
- return;
- }
+ PackageStateInternal packageState = mPm.getPackageStateInternal(mPackageName);
+ if (packageState == null) {
+ return;
+ }
- boolean wasLoading = ps.isLoading();
- // Due to asynchronous progress reporting, incomplete progress might be received
- // after the app is migrated off incremental. Ignore such progress updates.
- if (wasLoading) {
- ps.setLoadingProgress(progress);
- // Only report the state change when loading state changes from loading to not
- if (!ps.isLoading()) {
- // Unregister progress listener
- mPm.mIncrementalManager.unregisterLoadingProgressCallbacks(ps.getPathString());
- // Make sure the information is preserved
- mPm.scheduleWriteSettings();
- }
+ boolean wasLoading = packageState.isLoading();
+ // Due to asynchronous progress reporting, incomplete progress might be received
+ // after the app is migrated off incremental. Ignore such progress updates.
+ if (wasLoading) {
+ mPm.commitPackageStateMutation(null, mPackageName,
+ state -> state.setLoadingProgress(progress));
+ // Only report the state change when loading state changes from loading to not
+ if (Math.abs(1.0f - progress) < 0.00000001f) {
+ // Unregister progress listener
+ mPm.mIncrementalManager
+ .unregisterLoadingProgressCallbacks(packageState.getPathString());
+ // Make sure the information is preserved
+ mPm.scheduleWriteSettings();
}
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index b636c8e..50c26f4 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -347,7 +347,7 @@
commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags,
(parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
if (pkgSetting.getInstantApp(userId)) {
- mPm.mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.getAppId());
+ mPm.mInstantAppRegistry.addInstantApp(userId, pkgSetting.getAppId());
}
if (!IncrementalManager.isIncrementalPath(pkgSetting.getPathString())) {
@@ -2614,9 +2614,7 @@
? res.mRemovedInfo.mInstallerPackageName
: null;
- synchronized (mPm.mLock) {
- mPm.mInstantAppRegistry.onPackageInstalledLPw(res.mPkg, res.mNewUsers);
- }
+ mPm.notifyInstantAppPackageInstalled(res.mPkg.getPackageName(), res.mNewUsers);
// Determine the set of users who are adding this package for
// the first time vs. those who are seeing an update.
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 1de239e..ea6e458 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -18,12 +18,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.InstantAppInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
import android.content.pm.SigningDetails;
import android.graphics.Bitmap;
@@ -93,7 +94,7 @@
* pruning installed instant apps and meta-data for uninstalled instant apps
* when free space is needed.
*/
-class InstantAppRegistry implements Watchable, Snappable {
+public class InstantAppRegistry implements Watchable, Snappable {
private static final boolean DEBUG = false;
private static final String LOG_TAG = "InstantAppRegistry";
@@ -125,14 +126,17 @@
private static final String ATTR_NAME = "name";
private static final String ATTR_GRANTED = "granted";
- private final PackageManagerService mService;
+ private final Context mContext;
private final PermissionManagerServiceInternal mPermissionManager;
+ private final UserManagerInternal mUserManager;
+ private final DeletePackageHelper mDeletePackageHelper;
private final CookiePersistence mCookiePersistence;
- private final PackageManagerInternal mPmInternal;
+
+ private final Object mLock = new Object();
/** State for uninstalled instant apps */
@Watched
- @GuardedBy("mService.mLock")
+ @GuardedBy("mLock")
private final WatchedSparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps;
/**
@@ -142,12 +146,12 @@
* UserID -> TargetAppId -> InstantAppId
*/
@Watched
- @GuardedBy("mService.mLock")
+ @GuardedBy("mLock")
private final WatchedSparseArray<WatchedSparseArray<WatchedSparseBooleanArray>> mInstantGrants;
/** The set of all installed instant apps. UserID -> AppID */
@Watched
- @GuardedBy("mService.mLock")
+ @GuardedBy("mLock")
private final WatchedSparseArray<WatchedSparseBooleanArray> mInstalledInstantAppUids;
/**
@@ -159,6 +163,7 @@
* Watchable machinery
*/
private final WatchableImpl mWatchable = new WatchableImpl();
+
public void registerObserver(@NonNull Watcher observer) {
mWatchable.registerObserver(observer);
}
@@ -196,12 +201,14 @@
}};
}
- public InstantAppRegistry(PackageManagerService service,
- PermissionManagerServiceInternal permissionManager,
- PackageManagerInternal pmInternal) {
- mService = service;
+ public InstantAppRegistry(@NonNull Context context,
+ @NonNull PermissionManagerServiceInternal permissionManager,
+ @NonNull UserManagerInternal userManager,
+ @NonNull DeletePackageHelper deletePackageHelper) {
+ mContext = context;
mPermissionManager = permissionManager;
- mPmInternal = pmInternal;
+ mUserManager = userManager;
+ mDeletePackageHelper = deletePackageHelper;
mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper());
mUninstalledInstantApps = new WatchedSparseArray<List<UninstalledInstantAppState>>();
@@ -220,9 +227,10 @@
* The copy constructor is used by PackageManagerService to construct a snapshot.
*/
private InstantAppRegistry(InstantAppRegistry r) {
- mService = r.mService;
+ mContext = r.mContext;
mPermissionManager = r.mPermissionManager;
- mPmInternal = r.mPmInternal;
+ mUserManager = r.mUserManager;
+ mDeletePackageHelper = r.mDeletePackageHelper;
mCookiePersistence = null;
mUninstalledInstantApps = new WatchedSparseArray<List<UninstalledInstantAppState>>(
@@ -243,56 +251,44 @@
return mSnapshot.snapshot();
}
- @GuardedBy("mService.mLock")
- public byte[] getInstantAppCookieLPw(@NonNull String packageName,
- @UserIdInt int userId) {
- // Only installed packages can get their own cookie
- AndroidPackage pkg = mService.mPackages.get(packageName);
- if (pkg == null) {
+ public byte[] getInstantAppCookie(@NonNull AndroidPackage pkg, @UserIdInt int userId) {
+ synchronized (mLock) {
+ byte[] pendingCookie = mCookiePersistence.getPendingPersistCookieLPr(pkg, userId);
+ if (pendingCookie != null) {
+ return pendingCookie;
+ }
+ File cookieFile = peekInstantCookieFile(pkg.getPackageName(), userId);
+ if (cookieFile != null && cookieFile.exists()) {
+ try {
+ return IoUtils.readFileAsByteArray(cookieFile.toString());
+ } catch (IOException e) {
+ Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
+ }
+ }
return null;
}
-
- byte[] pendingCookie = mCookiePersistence.getPendingPersistCookieLPr(pkg, userId);
- if (pendingCookie != null) {
- return pendingCookie;
- }
- File cookieFile = peekInstantCookieFile(packageName, userId);
- if (cookieFile != null && cookieFile.exists()) {
- try {
- return IoUtils.readFileAsByteArray(cookieFile.toString());
- } catch (IOException e) {
- Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
- }
- }
- return null;
}
- @GuardedBy("mService.mLock")
- public boolean setInstantAppCookieLPw(@NonNull String packageName,
- @Nullable byte[] cookie, @UserIdInt int userId) {
- if (cookie != null && cookie.length > 0) {
- final int maxCookieSize = mService.mContext.getPackageManager()
- .getInstantAppCookieMaxBytes();
- if (cookie.length > maxCookieSize) {
- Slog.e(LOG_TAG, "Instant app cookie for package " + packageName + " size "
- + cookie.length + " bytes while max size is " + maxCookieSize);
- return false;
+ public boolean setInstantAppCookie(@NonNull AndroidPackage pkg,
+ @Nullable byte[] cookie, int instantAppCookieMaxBytes, @UserIdInt int userId) {
+ synchronized (mLock) {
+ if (cookie != null && cookie.length > 0) {
+ if (cookie.length > instantAppCookieMaxBytes) {
+ Slog.e(LOG_TAG, "Instant app cookie for package " + pkg.getPackageName()
+ + " size " + cookie.length + " bytes while max size is "
+ + instantAppCookieMaxBytes);
+ return false;
+ }
}
- }
- // Only an installed package can set its own cookie
- AndroidPackage pkg = mService.mPackages.get(packageName);
- if (pkg == null) {
- return false;
+ mCookiePersistence.schedulePersistLPw(userId, pkg, cookie);
+ return true;
}
-
- mCookiePersistence.schedulePersistLPw(userId, pkg, cookie);
- return true;
}
private void persistInstantApplicationCookie(@Nullable byte[] cookie,
@NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId) {
- synchronized (mService.mLock) {
+ synchronized (mLock) {
File appDir = getInstantApplicationDir(packageName, userId);
if (!appDir.exists() && !appDir.mkdirs()) {
Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
@@ -315,55 +311,54 @@
}
}
- public Bitmap getInstantAppIconLPw(@NonNull String packageName,
- @UserIdInt int userId) {
- File iconFile = new File(getInstantApplicationDir(packageName, userId),
- INSTANT_APP_ICON_FILE);
- if (iconFile.exists()) {
- return BitmapFactory.decodeFile(iconFile.toString());
- }
- return null;
- }
-
- public String getInstantAppAndroidIdLPw(@NonNull String packageName,
- @UserIdInt int userId) {
- File idFile = new File(getInstantApplicationDir(packageName, userId),
- INSTANT_APP_ANDROID_ID_FILE);
- if (idFile.exists()) {
- try {
- return IoUtils.readFileAsString(idFile.getAbsolutePath());
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Failed to read instant app android id file: " + idFile, e);
+ @Nullable
+ public Bitmap getInstantAppIcon(@NonNull String packageName, @UserIdInt int userId) {
+ synchronized (mLock) {
+ File iconFile = new File(getInstantApplicationDir(packageName, userId),
+ INSTANT_APP_ICON_FILE);
+ if (iconFile.exists()) {
+ return BitmapFactory.decodeFile(iconFile.toString());
}
+ return null;
}
- return generateInstantAppAndroidIdLPw(packageName, userId);
}
- private String generateInstantAppAndroidIdLPw(@NonNull String packageName,
- @UserIdInt int userId) {
- byte[] randomBytes = new byte[8];
- new SecureRandom().nextBytes(randomBytes);
- String id = HexEncoding.encodeToString(randomBytes, false /* upperCase */);
- File appDir = getInstantApplicationDir(packageName, userId);
- if (!appDir.exists() && !appDir.mkdirs()) {
- Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
+ @Nullable
+ public String getInstantAppAndroidId(@NonNull String packageName, @UserIdInt int userId) {
+ synchronized (mLock) {
+ File idFile = new File(getInstantApplicationDir(packageName, userId),
+ INSTANT_APP_ANDROID_ID_FILE);
+ if (idFile.exists()) {
+ try {
+ return IoUtils.readFileAsString(idFile.getAbsolutePath());
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Failed to read instant app android id file: " + idFile, e);
+ }
+ }
+
+ byte[] randomBytes = new byte[8];
+ new SecureRandom().nextBytes(randomBytes);
+ String id = HexEncoding.encodeToString(randomBytes, false /* upperCase */);
+ File appDir = getInstantApplicationDir(packageName, userId);
+ if (!appDir.exists() && !appDir.mkdirs()) {
+ Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
+ return id;
+ }
+ idFile = new File(getInstantApplicationDir(packageName, userId),
+ INSTANT_APP_ANDROID_ID_FILE);
+ try (FileOutputStream fos = new FileOutputStream(idFile)) {
+ fos.write(id.getBytes());
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e);
+ }
return id;
}
- File idFile = new File(getInstantApplicationDir(packageName, userId),
- INSTANT_APP_ANDROID_ID_FILE);
- try (FileOutputStream fos = new FileOutputStream(idFile)) {
- fos.write(id.getBytes());
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e);
- }
- return id;
-
}
- @GuardedBy("mService.mLock")
- public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) {
- List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId);
- List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId);
+ @Nullable
+ public List<InstantAppInfo> getInstantApps(@NonNull Computer computer, @UserIdInt int userId) {
+ List<InstantAppInfo> installedApps = getInstalledInstantApplications(computer, userId);
+ List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplications(computer, userId);
if (installedApps != null) {
if (uninstalledApps != null) {
installedApps.addAll(uninstalledApps);
@@ -373,123 +368,130 @@
return uninstalledApps;
}
- @GuardedBy("mService.mLock")
- public void onPackageInstalledLPw(@NonNull AndroidPackage pkg, @NonNull int[] userIds) {
- PackageStateInternal ps = mPmInternal.getPackageStateInternal(pkg.getPackageName());
- if (ps == null) {
+ public void onPackageInstalled(@NonNull Computer computer, @NonNull String packageName,
+ @NonNull int[] userIds) {
+ PackageStateInternal ps = computer.getPackageStateInternal(packageName);
+ AndroidPackage pkg = ps == null ? null : ps.getPkg();
+ if (pkg == null) {
return;
}
- for (int userId : userIds) {
- // Ignore not installed apps
- if (mService.mPackages.get(pkg.getPackageName()) == null
- || !ps.getUserStateOrDefault(userId).isInstalled()) {
- continue;
- }
+ synchronized (mLock) {
+ for (int userId : userIds) {
+ // Ignore not installed apps
+ if (!ps.getUserStateOrDefault(userId).isInstalled()) {
+ continue;
+ }
- // Propagate permissions before removing any state
- propagateInstantAppPermissionsIfNeeded(pkg, userId);
+ // Propagate permissions before removing any state
+ propagateInstantAppPermissionsIfNeeded(pkg, userId);
- // Track instant apps
- if (ps.getUserStateOrDefault(userId).isInstantApp()) {
- addInstantAppLPw(userId, ps.getAppId());
- }
+ // Track instant apps
+ if (ps.getUserStateOrDefault(userId).isInstantApp()) {
+ addInstantApp(userId, ps.getAppId());
+ }
- // Remove the in-memory state
- removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
- state.mInstantAppInfo.getPackageName().equals(pkg.getPackageName()),
- userId);
+ // Remove the in-memory state
+ removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
+ state.mInstantAppInfo.getPackageName().equals(pkg.getPackageName()),
+ userId);
- // Remove the on-disk state except the cookie
- File instantAppDir = getInstantApplicationDir(pkg.getPackageName(), userId);
- new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
- new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
+ // Remove the on-disk state except the cookie
+ File instantAppDir = getInstantApplicationDir(pkg.getPackageName(), userId);
+ new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
+ new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
- // If app signature changed - wipe the cookie
- File currentCookieFile = peekInstantCookieFile(pkg.getPackageName(), userId);
- if (currentCookieFile == null) {
- continue;
- }
+ // If app signature changed - wipe the cookie
+ File currentCookieFile = peekInstantCookieFile(pkg.getPackageName(), userId);
+ if (currentCookieFile == null) {
+ continue;
+ }
- String cookieName = currentCookieFile.getName();
- String currentCookieSha256 =
- cookieName.substring(INSTANT_APP_COOKIE_FILE_PREFIX.length(),
- cookieName.length() - INSTANT_APP_COOKIE_FILE_SIFFIX.length());
+ String cookieName = currentCookieFile.getName();
+ String currentCookieSha256 =
+ cookieName.substring(INSTANT_APP_COOKIE_FILE_PREFIX.length(),
+ cookieName.length() - INSTANT_APP_COOKIE_FILE_SIFFIX.length());
- // Before we used only the first signature to compute the SHA 256 but some
- // apps could be singed by multiple certs and the cert order is undefined.
- // We prefer the modern computation procedure where all certs are taken
- // into account but also allow the value from the old computation to avoid
- // data loss.
- if (pkg.getSigningDetails().checkCapability(currentCookieSha256,
- SigningDetails.CertCapabilities.INSTALLED_DATA)) {
- return;
- }
-
- // For backwards compatibility we accept match based on any signature, since we may have
- // recorded only the first for multiply-signed packages
- final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
- pkg.getSigningDetails().getSignatures());
- for (String s : signaturesSha256Digests) {
- if (s.equals(currentCookieSha256)) {
+ // Before we used only the first signature to compute the SHA 256 but some
+ // apps could be singed by multiple certs and the cert order is undefined.
+ // We prefer the modern computation procedure where all certs are taken
+ // into account but also allow the value from the old computation to avoid
+ // data loss.
+ if (pkg.getSigningDetails().checkCapability(currentCookieSha256,
+ SigningDetails.CertCapabilities.INSTALLED_DATA)) {
return;
}
- }
- // Sorry, you are out of luck - different signatures - nuke data
- Slog.i(LOG_TAG, "Signature for package " + pkg.getPackageName()
- + " changed - dropping cookie");
+ // For backwards compatibility we accept match based on any signature, since we may
+ // have recorded only the first for multiply-signed packages
+ final String[] signaturesSha256Digests =
+ PackageUtils.computeSignaturesSha256Digests(
+ pkg.getSigningDetails().getSignatures());
+ for (String s : signaturesSha256Digests) {
+ if (s.equals(currentCookieSha256)) {
+ return;
+ }
+ }
+
+ // Sorry, you are out of luck - different signatures - nuke data
+ Slog.i(LOG_TAG, "Signature for package " + pkg.getPackageName()
+ + " changed - dropping cookie");
// Make sure a pending write for the old signed app is cancelled
- mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
- currentCookieFile.delete();
+ mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
+ currentCookieFile.delete();
+ }
}
}
- @GuardedBy("mService.mLock")
- public void onPackageUninstalledLPw(@NonNull AndroidPackage pkg, @Nullable PackageSetting ps,
+ public void onPackageUninstalled(@NonNull AndroidPackage pkg, @NonNull PackageSetting ps,
@NonNull int[] userIds) {
if (ps == null) {
return;
}
- for (int userId : userIds) {
- if (mService.mPackages.get(pkg.getPackageName()) != null && ps.getInstalled(userId)) {
- continue;
- }
+ synchronized (mLock) {
+ for (int userId : userIds) {
+ if (ps.getInstalled(userId)) {
+ continue;
+ }
- if (ps.getInstantApp(userId)) {
- // Add a record for an uninstalled instant app
- addUninstalledInstantAppLPw(pkg, userId);
- removeInstantAppLPw(userId, ps.getAppId());
- } else {
- // Deleting an app prunes all instant state such as cookie
- deleteDir(getInstantApplicationDir(pkg.getPackageName(), userId));
- mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
- removeAppLPw(userId, ps.getAppId());
+ if (ps.getInstantApp(userId)) {
+ // Add a record for an uninstalled instant app
+ addUninstalledInstantAppLPw(ps, userId);
+ removeInstantAppLPw(userId, ps.getAppId());
+ } else {
+ // Deleting an app prunes all instant state such as cookie
+ deleteDir(getInstantApplicationDir(pkg.getPackageName(), userId));
+ mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
+ removeAppLPw(userId, ps.getAppId());
+ }
}
}
}
- @GuardedBy("mService.mLock")
- public void onUserRemovedLPw(int userId) {
- mUninstalledInstantApps.remove(userId);
- mInstalledInstantAppUids.remove(userId);
- mInstantGrants.remove(userId);
- deleteDir(getInstantApplicationsDir(userId));
+ public void onUserRemoved(int userId) {
+ synchronized (mLock) {
+ mUninstalledInstantApps.remove(userId);
+ mInstalledInstantAppUids.remove(userId);
+ mInstantGrants.remove(userId);
+ deleteDir(getInstantApplicationsDir(userId));
+ }
}
public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId,
int instantAppId) {
- final WatchedSparseArray<WatchedSparseBooleanArray> targetAppList =
- mInstantGrants.get(userId);
- if (targetAppList == null) {
- return false;
+ synchronized (mLock) {
+ final WatchedSparseArray<WatchedSparseBooleanArray> targetAppList =
+ mInstantGrants.get(userId);
+ if (targetAppList == null) {
+ return false;
+ }
+ final WatchedSparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
+ if (instantGrantList == null) {
+ return false;
+ }
+ return instantGrantList.get(instantAppId);
}
- final WatchedSparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
- if (instantGrantList == null) {
- return false;
- }
- return instantGrantList.get(instantAppId);
}
/**
@@ -503,51 +505,54 @@
* to the recipient
* @return {@code true} if access is granted.
*/
- @GuardedBy("mService.mLock")
- public boolean grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
+ public boolean grantInstantAccess(@UserIdInt int userId, @Nullable Intent intent,
int recipientUid, int instantAppId) {
- if (mInstalledInstantAppUids == null) {
- return false; // no instant apps installed; no need to grant
- }
- WatchedSparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
- if (instantAppList == null || !instantAppList.get(instantAppId)) {
- return false; // instant app id isn't installed; no need to grant
- }
- if (instantAppList.get(recipientUid)) {
- return false; // target app id is an instant app; no need to grant
- }
- if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
- final Set<String> categories = intent.getCategories();
- if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
- return false; // launched via VIEW/BROWSABLE intent; no need to grant
+ synchronized (mLock) {
+ if (mInstalledInstantAppUids == null) {
+ return false; // no instant apps installed; no need to grant
}
+ WatchedSparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
+ if (instantAppList == null || !instantAppList.get(instantAppId)) {
+ return false; // instant app id isn't installed; no need to grant
+ }
+ if (instantAppList.get(recipientUid)) {
+ return false; // target app id is an instant app; no need to grant
+ }
+ if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
+ final Set<String> categories = intent.getCategories();
+ if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
+ return false; // launched via VIEW/BROWSABLE intent; no need to grant
+ }
+ }
+ WatchedSparseArray<WatchedSparseBooleanArray> targetAppList = mInstantGrants.get(
+ userId);
+ if (targetAppList == null) {
+ targetAppList = new WatchedSparseArray<>();
+ mInstantGrants.put(userId, targetAppList);
+ }
+ WatchedSparseBooleanArray instantGrantList = targetAppList.get(recipientUid);
+ if (instantGrantList == null) {
+ instantGrantList = new WatchedSparseBooleanArray();
+ targetAppList.put(recipientUid, instantGrantList);
+ }
+ instantGrantList.put(instantAppId, true /*granted*/);
+ return true;
}
- WatchedSparseArray<WatchedSparseBooleanArray> targetAppList = mInstantGrants.get(userId);
- if (targetAppList == null) {
- targetAppList = new WatchedSparseArray<>();
- mInstantGrants.put(userId, targetAppList);
- }
- WatchedSparseBooleanArray instantGrantList = targetAppList.get(recipientUid);
- if (instantGrantList == null) {
- instantGrantList = new WatchedSparseBooleanArray();
- targetAppList.put(recipientUid, instantGrantList);
- }
- instantGrantList.put(instantAppId, true /*granted*/);
- return true;
}
- @GuardedBy("mService.mLock")
- public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) {
- WatchedSparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
- if (instantAppList == null) {
- instantAppList = new WatchedSparseBooleanArray();
- mInstalledInstantAppUids.put(userId, instantAppList);
+ public void addInstantApp(@UserIdInt int userId, int instantAppId) {
+ synchronized (mLock) {
+ WatchedSparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
+ if (instantAppList == null) {
+ instantAppList = new WatchedSparseBooleanArray();
+ mInstalledInstantAppUids.put(userId, instantAppList);
+ }
+ instantAppList.put(instantAppId, true /*installed*/);
}
- instantAppList.put(instantAppId, true /*installed*/);
onChanged();
}
- @GuardedBy("mService.mLock")
+ @GuardedBy("mLock")
private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) {
// remove from the installed list
if (mInstalledInstantAppUids == null) {
@@ -578,7 +583,7 @@
}
}
- @GuardedBy("mService.mLock")
+ @GuardedBy("mLock")
private void removeAppLPw(@UserIdInt int userId, int targetAppId) {
// remove from the installed list
if (mInstantGrants == null) {
@@ -593,11 +598,11 @@
onChanged();
}
- @GuardedBy("mService.mLock")
- private void addUninstalledInstantAppLPw(@NonNull AndroidPackage pkg,
+ @GuardedBy("mLock")
+ private void addUninstalledInstantAppLPw(@NonNull PackageStateInternal packageState,
@UserIdInt int userId) {
InstantAppInfo uninstalledApp = createInstantAppInfoForPackage(
- pkg, userId, false);
+ packageState, userId, false);
if (uninstalledApp == null) {
return;
}
@@ -612,7 +617,7 @@
uninstalledAppStates.add(uninstalledAppState);
writeUninstalledInstantAppMetadata(uninstalledApp, userId);
- writeInstantApplicationIconLPw(pkg, userId);
+ writeInstantApplicationIconLPw(packageState.getPkg(), userId);
}
private void writeInstantApplicationIconLPw(@NonNull AndroidPackage pkg,
@@ -624,7 +629,7 @@
// TODO(b/135203078): Remove toAppInfo call? Requires significant additions/changes to PM
Drawable icon = AndroidPackageUtils.generateAppInfoWithoutState(pkg)
- .loadIcon(mService.mContext.getPackageManager());
+ .loadIcon(mContext.getPackageManager());
final Bitmap bitmap;
if (icon instanceof BitmapDrawable) {
@@ -647,30 +652,30 @@
}
}
- @GuardedBy("mService.mLock")
- boolean hasInstantApplicationMetadataLPr(String packageName, int userId) {
- return hasUninstalledInstantAppStateLPr(packageName, userId)
- || hasInstantAppMetadataLPr(packageName, userId);
+ boolean hasInstantApplicationMetadata(String packageName, int userId) {
+ return hasUninstalledInstantAppState(packageName, userId)
+ || hasInstantAppMetadata(packageName, userId);
}
- @GuardedBy("mService.mLock")
- public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
+ public void deleteInstantApplicationMetadata(@NonNull String packageName,
@UserIdInt int userId) {
- removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
- state.mInstantAppInfo.getPackageName().equals(packageName),
- userId);
+ synchronized (mLock) {
+ removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
+ state.mInstantAppInfo.getPackageName().equals(packageName),
+ userId);
- File instantAppDir = getInstantApplicationDir(packageName, userId);
- new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
- new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
- new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
- File cookie = peekInstantCookieFile(packageName, userId);
- if (cookie != null) {
- cookie.delete();
+ File instantAppDir = getInstantApplicationDir(packageName, userId);
+ new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
+ new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
+ new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
+ File cookie = peekInstantCookieFile(packageName, userId);
+ if (cookie != null) {
+ cookie.delete();
+ }
}
}
- @GuardedBy("mService.mLock")
+ @GuardedBy("mLock")
private void removeUninstalledInstantAppStateLPw(
@NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) {
if (mUninstalledInstantApps == null) {
@@ -696,27 +701,28 @@
}
}
- @GuardedBy("mService.mLock")
- private boolean hasUninstalledInstantAppStateLPr(String packageName, @UserIdInt int userId) {
- if (mUninstalledInstantApps == null) {
- return false;
- }
- final List<UninstalledInstantAppState> uninstalledAppStates =
- mUninstalledInstantApps.get(userId);
- if (uninstalledAppStates == null) {
- return false;
- }
- final int appCount = uninstalledAppStates.size();
- for (int i = 0; i < appCount; i++) {
- final UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
- if (packageName.equals(uninstalledAppState.mInstantAppInfo.getPackageName())) {
- return true;
+ private boolean hasUninstalledInstantAppState(String packageName, @UserIdInt int userId) {
+ synchronized (mLock) {
+ if (mUninstalledInstantApps == null) {
+ return false;
}
+ final List<UninstalledInstantAppState> uninstalledAppStates =
+ mUninstalledInstantApps.get(userId);
+ if (uninstalledAppStates == null) {
+ return false;
+ }
+ final int appCount = uninstalledAppStates.size();
+ for (int i = 0; i < appCount; i++) {
+ final UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
+ if (packageName.equals(uninstalledAppState.mInstantAppInfo.getPackageName())) {
+ return true;
+ }
+ }
+ return false;
}
- return false;
}
- private boolean hasInstantAppMetadataLPr(String packageName, @UserIdInt int userId) {
+ private boolean hasInstantAppMetadata(String packageName, @UserIdInt int userId) {
final File instantAppDir = getInstantApplicationDir(packageName, userId);
return new File(instantAppDir, INSTANT_APP_METADATA_FILE).exists()
|| new File(instantAppDir, INSTANT_APP_ICON_FILE).exists()
@@ -724,37 +730,41 @@
|| peekInstantCookieFile(packageName, userId) != null;
}
- void pruneInstantApps() {
+ void pruneInstantApps(@NonNull Computer computer) {
final long maxInstalledCacheDuration = Settings.Global.getLong(
- mService.mContext.getContentResolver(),
+ mContext.getContentResolver(),
Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
final long maxUninstalledCacheDuration = Settings.Global.getLong(
- mService.mContext.getContentResolver(),
+ mContext.getContentResolver(),
Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
try {
- pruneInstantApps(Long.MAX_VALUE,
+ pruneInstantApps(computer, Long.MAX_VALUE,
maxInstalledCacheDuration, maxUninstalledCacheDuration);
} catch (IOException e) {
Slog.e(LOG_TAG, "Error pruning installed and uninstalled instant apps", e);
}
}
- boolean pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration) {
+ boolean pruneInstalledInstantApps(@NonNull Computer computer, long neededSpace,
+ long maxInstalledCacheDuration) {
try {
- return pruneInstantApps(neededSpace, maxInstalledCacheDuration, Long.MAX_VALUE);
+ return pruneInstantApps(computer, neededSpace, maxInstalledCacheDuration,
+ Long.MAX_VALUE);
} catch (IOException e) {
Slog.e(LOG_TAG, "Error pruning installed instant apps", e);
return false;
}
}
- boolean pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration) {
+ boolean pruneUninstalledInstantApps(@NonNull Computer computer, long neededSpace,
+ long maxUninstalledCacheDuration) {
try {
- return pruneInstantApps(neededSpace, Long.MAX_VALUE, maxUninstalledCacheDuration);
+ return pruneInstantApps(computer, neededSpace, Long.MAX_VALUE,
+ maxUninstalledCacheDuration);
} catch (IOException e) {
Slog.e(LOG_TAG, "Error pruning uninstalled instant apps", e);
return false;
@@ -774,9 +784,9 @@
*
* @throws IOException
*/
- private boolean pruneInstantApps(long neededSpace, long maxInstalledCacheDuration,
- long maxUninstalledCacheDuration) throws IOException {
- final StorageManager storage = mService.mContext.getSystemService(StorageManager.class);
+ private boolean pruneInstantApps(@NonNull Computer computer, long neededSpace,
+ long maxInstalledCacheDuration, long maxUninstalledCacheDuration) throws IOException {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
final File file = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL);
if (file.getUsableSpace() >= neededSpace) {
@@ -789,105 +799,105 @@
final long now = System.currentTimeMillis();
// Prune first installed instant apps
- synchronized (mService.mLock) {
- allUsers = mService.mUserManager.getUserIds();
+ allUsers = mUserManager.getUserIds();
- final int packageCount = mService.mPackages.size();
- for (int i = 0; i < packageCount; i++) {
- final AndroidPackage pkg = mService.mPackages.valueAt(i);
- final PackageStateInternal ps =
- mPmInternal.getPackageStateInternal(pkg.getPackageName());
- if (ps == null) {
- continue;
- }
-
- if (now - ps.getTransientState().getLatestPackageUseTimeInMills()
- < maxInstalledCacheDuration) {
- continue;
- }
-
- boolean installedOnlyAsInstantApp = false;
- for (int userId : allUsers) {
- final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
- if (userState.isInstalled()) {
- if (userState.isInstantApp()) {
- installedOnlyAsInstantApp = true;
- } else {
- installedOnlyAsInstantApp = false;
- break;
- }
- }
- }
- if (installedOnlyAsInstantApp) {
- if (packagesToDelete == null) {
- packagesToDelete = new ArrayList<>();
- }
- packagesToDelete.add(pkg.getPackageName());
- }
+ final ArrayMap<String, ? extends PackageStateInternal> packageStates =
+ computer.getPackageStates();
+ final int packageStateCount = packageStates.size();
+ for (int i = 0; i < packageStateCount; i++) {
+ final PackageStateInternal ps = packageStates.valueAt(i);
+ final AndroidPackage pkg = ps == null ? null : ps.getPkg();
+ if (pkg == null) {
+ continue;
}
- if (packagesToDelete != null) {
- packagesToDelete.sort((String lhs, String rhs) -> {
- final AndroidPackage lhsPkg = mService.mPackages.get(lhs);
- final AndroidPackage rhsPkg = mService.mPackages.get(rhs);
- if (lhsPkg == null && rhsPkg == null) {
- return 0;
- } else if (lhsPkg == null) {
- return -1;
- } else if (rhsPkg == null) {
- return 1;
+ if (now - ps.getTransientState().getLatestPackageUseTimeInMills()
+ < maxInstalledCacheDuration) {
+ continue;
+ }
+
+ boolean installedOnlyAsInstantApp = false;
+ for (int userId : allUsers) {
+ final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+ if (userState.isInstalled()) {
+ if (userState.isInstantApp()) {
+ installedOnlyAsInstantApp = true;
} else {
- final PackageStateInternal lhsPs =
- mPmInternal.getPackageStateInternal(lhsPkg.getPackageName());
- if (lhsPs == null) {
- return 0;
- }
-
- final PackageStateInternal rhsPs =
- mPmInternal.getPackageStateInternal(rhsPkg.getPackageName());
- if (rhsPs == null) {
- return 0;
- }
-
- if (lhsPs.getTransientState().getLatestPackageUseTimeInMills() >
- rhsPs.getTransientState().getLatestPackageUseTimeInMills()) {
- return 1;
- } else if (lhsPs.getTransientState().getLatestPackageUseTimeInMills() <
- rhsPs.getTransientState().getLatestPackageUseTimeInMills()) {
- return -1;
- } else if (
- PackageStateUtils.getEarliestFirstInstallTime(lhsPs.getUserStates())
- > PackageStateUtils.getEarliestFirstInstallTime(
- rhsPs.getUserStates())) {
- return 1;
- } else {
- return -1;
- }
+ installedOnlyAsInstantApp = false;
+ break;
}
- });
+ }
+ }
+ if (installedOnlyAsInstantApp) {
+ if (packagesToDelete == null) {
+ packagesToDelete = new ArrayList<>();
+ }
+ packagesToDelete.add(pkg.getPackageName());
}
}
if (packagesToDelete != null) {
- final int packageCount = packagesToDelete.size();
- final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mService);
- for (int i = 0; i < packageCount; i++) {
- final String packageToDelete = packagesToDelete.get(i);
- if (deletePackageHelper.deletePackageX(packageToDelete,
- PackageManager.VERSION_CODE_HIGHEST,
- UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS,
- true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) {
- if (file.getUsableSpace() >= neededSpace) {
- return true;
+ packagesToDelete.sort((String lhs, String rhs) -> {
+ final PackageStateInternal lhsPkgState = packageStates.get(lhs);
+ final PackageStateInternal rhsPkgState = packageStates.get(rhs);
+ final AndroidPackage lhsPkg = lhsPkgState == null ? null : lhsPkgState.getPkg();
+ final AndroidPackage rhsPkg = rhsPkgState == null ? null : rhsPkgState.getPkg();
+ if (lhsPkg == null && rhsPkg == null) {
+ return 0;
+ } else if (lhsPkg == null) {
+ return -1;
+ } else if (rhsPkg == null) {
+ return 1;
+ } else {
+ final PackageStateInternal lhsPs =
+ packageStates.get(lhsPkg.getPackageName());
+ if (lhsPs == null) {
+ return 0;
+ }
+
+ final PackageStateInternal rhsPs =
+ packageStates.get(rhsPkg.getPackageName());
+ if (rhsPs == null) {
+ return 0;
+ }
+
+ if (lhsPs.getTransientState().getLatestPackageUseTimeInMills() >
+ rhsPs.getTransientState().getLatestPackageUseTimeInMills()) {
+ return 1;
+ } else if (lhsPs.getTransientState().getLatestPackageUseTimeInMills() <
+ rhsPs.getTransientState().getLatestPackageUseTimeInMills()) {
+ return -1;
+ } else if (
+ PackageStateUtils.getEarliestFirstInstallTime(lhsPs.getUserStates())
+ > PackageStateUtils.getEarliestFirstInstallTime(
+ rhsPs.getUserStates())) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ });
+ }
+
+ synchronized (mLock) {
+ if (packagesToDelete != null) {
+ final int packageCount = packagesToDelete.size();
+ for (int i = 0; i < packageCount; i++) {
+ final String packageToDelete = packagesToDelete.get(i);
+ if (mDeletePackageHelper.deletePackageX(packageToDelete,
+ PackageManager.VERSION_CODE_HIGHEST,
+ UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS,
+ true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) {
+ if (file.getUsableSpace() >= neededSpace) {
+ return true;
+ }
}
}
}
- }
- // Prune uninstalled instant apps
- synchronized (mService.mLock) {
+ // Prune uninstalled instant apps
// TODO: Track last used time for uninstalled instant apps for better pruning
- for (int userId : UserManagerService.getInstance().getUserIds()) {
+ for (int userId : mUserManager.getUserIds()) {
// Prune in-memory state
removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
@@ -928,21 +938,19 @@
return false;
}
- @GuardedBy("mService.mLock")
- private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(
- @UserIdInt int userId) {
+ private @Nullable List<InstantAppInfo> getInstalledInstantApplications(
+ @NonNull Computer computer, @UserIdInt int userId) {
List<InstantAppInfo> result = null;
- final int packageCount = mService.mPackages.size();
+ final ArrayMap<String, ? extends PackageStateInternal> packageStates =
+ computer.getPackageStates();
+ final int packageCount = packageStates.size();
for (int i = 0; i < packageCount; i++) {
- final AndroidPackage pkg = mService.mPackages.valueAt(i);
- final PackageStateInternal ps =
- mPmInternal.getPackageStateInternal(pkg.getPackageName());
+ final PackageStateInternal ps = packageStates.valueAt(i);
if (ps == null || !ps.getUserStateOrDefault(userId).isInstantApp()) {
continue;
}
- final InstantAppInfo info = createInstantAppInfoForPackage(
- pkg, userId, true);
+ final InstantAppInfo info = createInstantAppInfoForPackage(ps, userId, true);
if (info == null) {
continue;
}
@@ -956,14 +964,10 @@
}
private @NonNull
- InstantAppInfo createInstantAppInfoForPackage(
- @NonNull AndroidPackage pkg, @UserIdInt int userId,
- boolean addApplicationInfo) {
- PackageStateInternal ps = mPmInternal.getPackageStateInternal(pkg.getPackageName());
- if (ps == null) {
- return null;
- }
- if (!ps.getUserStateOrDefault(userId).isInstalled()) {
+ InstantAppInfo createInstantAppInfoForPackage(@NonNull PackageStateInternal ps,
+ @UserIdInt int userId, boolean addApplicationInfo) {
+ AndroidPackage pkg = ps.getPkg();
+ if (pkg == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
return null;
}
@@ -982,17 +986,18 @@
if (addApplicationInfo) {
return new InstantAppInfo(appInfo, requestedPermissions, grantedPermissions);
} else {
+ // TODO: PMS lock re-entry
return new InstantAppInfo(appInfo.packageName,
- appInfo.loadLabel(mService.mContext.getPackageManager()),
+ appInfo.loadLabel(mContext.getPackageManager()),
requestedPermissions, grantedPermissions);
}
}
- @GuardedBy("mService.mLock")
- private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr(
+ @Nullable
+ private List<InstantAppInfo> getUninstalledInstantApplications(@NonNull Computer computer,
@UserIdInt int userId) {
List<UninstalledInstantAppState> uninstalledAppStates =
- getUninstalledInstantAppStatesLPr(userId);
+ getUninstalledInstantAppStates(userId);
if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
return null;
}
@@ -1009,6 +1014,7 @@
return uninstalledApps;
}
+ @SuppressLint("MissingPermission")
private void propagateInstantAppPermissionsIfNeeded(@NonNull AndroidPackage pkg,
@UserIdInt int userId) {
InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
@@ -1025,8 +1031,9 @@
final boolean propagatePermission = canPropagatePermission(grantedPermission);
if (propagatePermission && pkg.getRequestedPermissions().contains(
grantedPermission)) {
- mService.grantRuntimePermission(pkg.getPackageName(), grantedPermission,
- userId);
+ mContext.getSystemService(PermissionManager.class)
+ .grantRuntimePermission(pkg.getPackageName(), grantedPermission,
+ UserHandle.of(userId));
}
}
} finally {
@@ -1035,8 +1042,8 @@
}
private boolean canPropagatePermission(@NonNull String permissionName) {
- final PermissionManager permissionManager = mService.mContext.getSystemService(
- PermissionManager.class);
+ final PermissionManager permissionManager =
+ mContext.getSystemService(PermissionManager.class);
final PermissionInfo permissionInfo = permissionManager.getPermissionInfo(permissionName,
0);
return permissionInfo != null
@@ -1050,16 +1057,19 @@
private @NonNull
InstantAppInfo peekOrParseUninstalledInstantAppInfo(
@NonNull String packageName, @UserIdInt int userId) {
- if (mUninstalledInstantApps != null) {
- List<UninstalledInstantAppState> uninstalledAppStates =
- mUninstalledInstantApps.get(userId);
- if (uninstalledAppStates != null) {
- final int appCount = uninstalledAppStates.size();
- for (int i = 0; i < appCount; i++) {
- UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
- if (uninstalledAppState.mInstantAppInfo
- .getPackageName().equals(packageName)) {
- return uninstalledAppState.mInstantAppInfo;
+ synchronized (mLock) {
+ if (mUninstalledInstantApps != null) {
+ List<UninstalledInstantAppState> uninstalledAppStates =
+ mUninstalledInstantApps.get(userId);
+ if (uninstalledAppStates != null) {
+ final int appCount = uninstalledAppStates.size();
+ for (int i = 0; i < appCount; i++) {
+ UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(
+ i);
+ if (uninstalledAppState.mInstantAppInfo
+ .getPackageName().equals(packageName)) {
+ return uninstalledAppState.mInstantAppInfo;
+ }
}
}
}
@@ -1075,14 +1085,15 @@
return uninstalledAppState.mInstantAppInfo;
}
- @GuardedBy("mService.mLock")
- private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr(
- @UserIdInt int userId) {
+ @Nullable
+ private List<UninstalledInstantAppState> getUninstalledInstantAppStates(@UserIdInt int userId) {
List<UninstalledInstantAppState> uninstalledAppStates = null;
- if (mUninstalledInstantApps != null) {
- uninstalledAppStates = mUninstalledInstantApps.get(userId);
- if (uninstalledAppStates != null) {
- return uninstalledAppStates;
+ synchronized (mLock) {
+ if (mUninstalledInstantApps != null) {
+ uninstalledAppStates = mUninstalledInstantApps.get(userId);
+ if (uninstalledAppStates != null) {
+ return uninstalledAppStates;
+ }
}
}
@@ -1109,7 +1120,9 @@
}
}
- mUninstalledInstantApps.put(userId, uninstalledAppStates);
+ synchronized (mLock) {
+ mUninstalledInstantApps.put(userId, uninstalledAppStates);
+ }
return uninstalledAppStates;
}
@@ -1246,7 +1259,7 @@
serializer.startTag(null, TAG_PACKAGE);
serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel(
- mService.mContext.getPackageManager()).toString());
+ mContext.getPackageManager()).toString());
serializer.startTag(null, TAG_PERMISSIONS);
for (String permission : instantApp.getRequestedPermissions()) {
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index db346da..afca350 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -31,6 +31,7 @@
import android.util.TypedXmlSerializer;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.utils.WatchedArrayMap;
import org.xmlpull.v1.XmlPullParser;
@@ -353,9 +354,9 @@
return mKeySets.get(id) != null;
}
- public boolean shouldCheckUpgradeKeySetLocked(PackageSetting oldPs, int scanFlags) {
+ public boolean shouldCheckUpgradeKeySetLocked(PackageStateInternal oldPs, int scanFlags) {
// Can't rotate keys during boot or if sharedUser.
- if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
+ if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || (oldPs.getSharedUser() != null)
|| !oldPs.getKeySetData().isUsingUpgradeKeySets()) {
return false;
}
@@ -374,7 +375,7 @@
return true;
}
- public boolean checkUpgradeKeySetLocked(PackageSetting oldPS, AndroidPackage pkg) {
+ public boolean checkUpgradeKeySetLocked(PackageStateInternal oldPS, AndroidPackage pkg) {
// Upgrade keysets are being used. Determine if new package has a superset of the
// required keys.
long[] upgradeKeySets = oldPS.getKeySetData().getUpgradeKeySets();
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index c125fe1..bd00914 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -30,6 +30,7 @@
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.storage.StorageManager;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -376,12 +377,13 @@
}
// Make a copy of all packages and look into each package.
- final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
- final ArrayList<AndroidPackage> pkgs = new ArrayList<>();
- pmInt.forEachPackage(pkgs::add);
+ final ArrayMap<String, ? extends PackageStateInternal> packageStates =
+ LocalServices.getService(PackageManagerInternal.class).getPackageStates();
int packagePaths = 0;
int pathsSuccessful = 0;
- for (AndroidPackage pkg : pkgs) {
+ for (int index = 0; index < packageStates.size(); index++) {
+ final PackageStateInternal packageState = packageStates.valueAt(index);
+ final AndroidPackage pkg = packageState.getPkg();
if (pkg == null) {
continue;
}
@@ -404,10 +406,9 @@
continue;
}
- PackageStateInternal pkgSetting = pmInt.getPackageStateInternal(pkg.getPackageName());
final String[] instructionSets = getAppDexInstructionSets(
- AndroidPackageUtils.getPrimaryCpuAbi(pkg, pkgSetting),
- AndroidPackageUtils.getSecondaryCpuAbi(pkg, pkgSetting));
+ AndroidPackageUtils.getPrimaryCpuAbi(pkg, packageState),
+ AndroidPackageUtils.getSecondaryCpuAbi(pkg, packageState));
final List<String> paths =
AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg);
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index d1ea41a..ec71940 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -34,9 +34,7 @@
import static com.android.server.pm.PackageManagerService.POST_INSTALL;
import static com.android.server.pm.PackageManagerService.PRUNE_UNUSED_STATIC_SHARED_LIBRARIES;
import static com.android.server.pm.PackageManagerService.SEND_PENDING_BROADCAST;
-import static com.android.server.pm.PackageManagerService.SNAPSHOT_UNCORK;
import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.PackageManagerService.TRACE_SNAPSHOTS;
import static com.android.server.pm.PackageManagerService.WRITE_PACKAGE_LIST;
import static com.android.server.pm.PackageManagerService.WRITE_PACKAGE_RESTRICTIONS;
import static com.android.server.pm.PackageManagerService.WRITE_SETTINGS;
@@ -379,13 +377,6 @@
mPm.mDomainVerificationManager.runMessage(messageCode, object);
break;
}
- case SNAPSHOT_UNCORK: {
- int corking = mPm.sSnapshotCorked.decrementAndGet();
- if (TRACE_SNAPSHOTS && corking == 0) {
- Log.e(TAG, "snapshot: corking goes to zero in message handler");
- }
- break;
- }
case PRUNE_UNUSED_STATIC_SHARED_LIBRARIES: {
try {
mPm.mInjector.getSharedLibrariesImpl().pruneUnusedStaticSharedLibraries(
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e0d404a..79cfa06 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -110,7 +110,6 @@
import android.content.pm.PackageManager.Property;
import android.content.pm.PackageManager.PropertyLocation;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageManagerInternal.PackageListObserver;
import android.content.pm.PackageManagerInternal.PrivateResolveFlags;
import android.content.pm.PackagePartitions;
import android.content.pm.ParceledListSlice;
@@ -232,11 +231,14 @@
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUtils;
+import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.pm.pkg.component.ParsedInstrumentation;
import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.mutate.PackageStateMutator;
import com.android.server.pm.pkg.mutate.PackageStateWrite;
+import com.android.server.pm.pkg.mutate.PackageUserStateWrite;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationService;
@@ -592,12 +594,12 @@
// the suffix "Locked". Some methods may use the legacy suffix "LP"
final PackageManagerTracedLock mLock;
+ // Ensures order of overlay updates until data storage can be moved to overlay code
+ private final PackageManagerTracedLock mOverlayPathsLock = new PackageManagerTracedLock();
+
// Lock alias for doing package state mutation
private final PackageManagerTracedLock mPackageStateWriteLock;
- // Lock alias to track syncing a consistent Computer
- private final PackageManagerTracedLock mLiveComputerSyncLock;
-
private final PackageStateMutator mPackageStateMutator = new PackageStateMutator(
this::getPackageSettingForMutation,
this::getDisabledPackageSettingForMutation);
@@ -682,23 +684,11 @@
@Watched
final InstantAppRegistry mInstantAppRegistry;
- @GuardedBy("mLock")
- int mChangedPackagesSequenceNumber;
- /**
- * List of changed [installed, removed or updated] packages.
- * mapping from user id -> sequence number -> package name
- */
- @GuardedBy("mLock")
- final SparseArray<SparseArray<String>> mChangedPackages = new SparseArray<>();
- /**
- * The sequence number of the last change to a package.
- * mapping from user id -> package name -> sequence number
- */
- @GuardedBy("mLock")
- final SparseArray<Map<String, Integer>> mChangedPackagesSequenceNumbers = new SparseArray<>();
+ @NonNull
+ final ChangedPackagesTracker mChangedPackagesTracker;
- @GuardedBy("mLock")
- final private ArraySet<PackageListObserver> mPackageListObservers = new ArraySet<>();
+ @NonNull
+ private final PackageObserverHelper mPackageObserverHelper = new PackageObserverHelper();
private final ModuleInfoProvider mModuleInfoProvider;
@@ -736,21 +726,11 @@
ApplicationPackageManager.invalidateGetPackagesForUidCache();
ApplicationPackageManager.disableGetPackagesForUidCache();
ApplicationPackageManager.invalidateHasSystemFeatureCache();
-
- // Avoid invalidation-thrashing by preventing cache invalidations from causing property
- // writes if the cache isn't enabled yet. We re-enable writes later when we're
- // done initializing.
- sSnapshotCorked.incrementAndGet();
PackageManager.corkPackageInfoCache();
}
@Override
public void enablePackageCaches() {
- // Uncork cache invalidations and allow clients to cache package information.
- int corking = sSnapshotCorked.decrementAndGet();
- if (TRACE_SNAPSHOTS && corking == 0) {
- Log.i(TAG, "snapshot: corking returns to 0");
- }
PackageManager.uncorkPackageInfoCache();
}
}
@@ -866,8 +846,10 @@
@Watched
final ComponentResolver mComponentResolver;
- // List of packages names to keep cached, even if they are uninstalled for all users
- private List<String> mKeepUninstalledPackages;
+ // Set of packages names to keep cached, even if they are uninstalled for all users
+ @GuardedBy("mKeepUninstalledPackages")
+ @NonNull
+ private final ArraySet<String> mKeepUninstalledPackages = new ArraySet<>();
// Cached reference to IDevicePolicyManager.
private IDevicePolicyManager mDevicePolicyManager = null;
@@ -904,8 +886,7 @@
static final int INTEGRITY_VERIFICATION_COMPLETE = 25;
static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26;
static final int DOMAIN_VERIFICATION = 27;
- static final int SNAPSHOT_UNCORK = 28;
- static final int PRUNE_UNUSED_STATIC_SHARED_LIBRARIES = 29;
+ static final int PRUNE_UNUSED_STATIC_SHARED_LIBRARIES = 28;
static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
private static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
@@ -1018,7 +999,8 @@
isolatedOwners = mIsolatedOwnersSnapshot.snapshot();
packages = mPackagesSnapshot.snapshot();
instrumentation = mInstrumentationSnapshot.snapshot();
- resolveComponentName = mResolveComponentName.clone();
+ resolveComponentName = mResolveComponentName == null
+ ? null : mResolveComponentName.clone();
resolveActivity = new ActivityInfo(mResolveActivity);
instantAppInstallerActivity =
(mInstantAppInstallerActivity == null)
@@ -1075,10 +1057,6 @@
// should only be set true while holding mLock. However, the attribute id guaranteed
// to be set false only while mLock and mSnapshotLock are both held.
private static final AtomicBoolean sSnapshotInvalid = new AtomicBoolean(true);
- // If true, the snapshot is corked. Do not create a new snapshot but use the live
- // computer. This throttles snapshot creation during periods of churn in Package
- // Manager.
- static final AtomicInteger sSnapshotCorked = new AtomicInteger(0);
static final ThreadLocal<ThreadComputer> sThreadComputer =
ThreadLocal.withInitial(ThreadComputer::new);
@@ -1095,16 +1073,9 @@
* The snapshot statistics. These are collected to track performance and to identify
* situations in which the snapshots are misbehaving.
*/
+ @Nullable
private final SnapshotStatistics mSnapshotStatistics;
- // The snapshot disable/enable switch. An image with the flag set true uses snapshots
- // and an image with the flag set false does not use snapshots.
- private static final boolean SNAPSHOT_ENABLED = true;
-
- // The per-instance snapshot disable/enable flag. This is generally set to false in
- // test instances and set to SNAPSHOT_ENABLED in operational instances.
- private final boolean mSnapshotEnabled;
-
/**
* Return the live computer.
*/
@@ -1117,17 +1088,10 @@
* The live computer will be returned if snapshots are disabled.
*/
Computer snapshotComputer() {
- if (!mSnapshotEnabled) {
- return mLiveComputer;
- }
if (Thread.holdsLock(mLock)) {
// If the current thread holds mLock then it may have modified state but not
// yet invalidated the snapshot. Always give the thread the live computer.
return mLiveComputer;
- } else if (sSnapshotCorked.get() > 0) {
- // Snapshots are corked, which means new ones should not be built right now.
- mSnapshotStatistics.corked();
- return mLiveComputer;
}
synchronized (mSnapshotLock) {
// This synchronization block serializes access to the snapshot computer and
@@ -1168,7 +1132,9 @@
mSnapshotComputer = new ComputerEngine(args);
final long done = SystemClock.currentTimeMicro();
- mSnapshotStatistics.rebuild(now, done, hits);
+ if (mSnapshotStatistics != null) {
+ mSnapshotStatistics.rebuild(now, done, hits);
+ }
}
/**
@@ -1397,32 +1363,41 @@
}
}
- void scheduleWritePackageRestrictionsLocked(UserHandle user) {
+ void scheduleWritePackageRestrictions(UserHandle user) {
final int userId = user == null ? UserHandle.USER_ALL : user.getIdentifier();
- scheduleWritePackageRestrictionsLocked(userId);
+ scheduleWritePackageRestrictions(userId);
}
- void scheduleWritePackageRestrictionsLocked(int userId) {
+ void scheduleWritePackageRestrictions(int userId) {
invalidatePackageInfoCache();
- final int[] userIds = (userId == UserHandle.USER_ALL)
- ? mUserManager.getUserIds() : new int[]{userId};
- for (int nextUserId : userIds) {
- if (!mUserManager.exists(nextUserId)) return;
-
- mDirtyUsers.add(nextUserId);
- if (!mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) {
- mHandler.sendEmptyMessageDelayed(WRITE_PACKAGE_RESTRICTIONS, WRITE_SETTINGS_DELAY);
+ if (userId == UserHandle.USER_ALL) {
+ synchronized (mDirtyUsers) {
+ for (int aUserId : mUserManager.getUserIds()) {
+ mDirtyUsers.add(aUserId);
+ }
}
+ } else {
+ if (!mUserManager.exists(userId)) {
+ return;
+ }
+ synchronized (mDirtyUsers) {
+ mDirtyUsers.add(userId);
+ }
+ }
+ if (!mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) {
+ mHandler.sendEmptyMessageDelayed(WRITE_PACKAGE_RESTRICTIONS, WRITE_SETTINGS_DELAY);
}
}
void writePendingRestrictions() {
synchronized (mLock) {
mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
- for (int userId : mDirtyUsers) {
- mSettings.writePackageRestrictionsLPr(userId);
+ synchronized (mDirtyUsers) {
+ for (int userId : mDirtyUsers) {
+ mSettings.writePackageRestrictionsLPr(userId);
+ }
+ mDirtyUsers.clear();
}
- mDirtyUsers.clear();
}
}
@@ -1431,7 +1406,9 @@
mHandler.removeMessages(WRITE_SETTINGS);
mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
writeSettingsLPrTEMP();
- mDirtyUsers.clear();
+ synchronized (mDirtyUsers) {
+ mDirtyUsers.clear();
+ }
}
}
@@ -1523,25 +1500,19 @@
PackageManagerService m = new PackageManagerService(injector, onlyCore, factoryTest,
PackagePartitions.FINGERPRINT, Build.IS_ENG, Build.IS_USERDEBUG,
- Build.VERSION.SDK_INT, Build.VERSION.INCREMENTAL, SNAPSHOT_ENABLED);
+ Build.VERSION.SDK_INT, Build.VERSION.INCREMENTAL);
t.traceEnd(); // "create package manager"
final CompatChange.ChangeListener selinuxChangeListener = packageName -> {
synchronized (m.mInstallLock) {
- final AndroidPackage pkg;
- final PackageSetting ps;
- final SharedUserSetting sharedUser;
- final String oldSeInfo;
- synchronized (m.mLock) {
- ps = m.mSettings.getPackageLPr(packageName);
- if (ps == null) {
- Slog.e(TAG, "Failed to find package setting " + packageName);
- return;
- }
- pkg = ps.getPkg();
- sharedUser = ps.getSharedUser();
- oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+ final PackageStateInternal packageState = m.getPackageStateInternal(packageName);
+ if (packageState == null) {
+ Slog.e(TAG, "Failed to find package setting " + packageName);
+ return;
}
+ AndroidPackage pkg = packageState.getPkg();
+ SharedUserApi sharedUser = packageState.getSharedUser();
+ String oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
if (pkg == null) {
Slog.e(TAG, "Failed to find package " + packageName);
@@ -1553,7 +1524,8 @@
if (!newSeInfo.equals(oldSeInfo)) {
Slog.i(TAG, "Updating seInfo for package " + packageName + " from: "
+ oldSeInfo + " to: " + newSeInfo);
- ps.getPkgState().setOverrideSeInfo(newSeInfo);
+ m.commitPackageStateMutation(null, packageName,
+ state -> state.setOverrideSeInfo(newSeInfo));
m.mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
}
}
@@ -1573,31 +1545,51 @@
/** Install/uninstall system packages for all users based on their user-type, as applicable. */
private void installAllowlistedSystemPackages() {
- synchronized (mLock) {
- final boolean scheduleWrite = mUserManager.installWhitelistedSystemPackages(
- isFirstBoot(), isDeviceUpgrading(), mExistingPackages);
- if (scheduleWrite) {
- scheduleWritePackageRestrictionsLocked(UserHandle.USER_ALL);
- scheduleWriteSettings();
- }
+ if (mUserManager.installWhitelistedSystemPackages(isFirstBoot(), isDeviceUpgrading(),
+ mExistingPackages)) {
+ scheduleWritePackageRestrictions(UserHandle.USER_ALL);
+ scheduleWriteSettings();
}
}
// Link watchables to the class
- private void registerObserver() {
- mPackages.registerObserver(mWatcher);
- mSharedLibraries.registerObserver(mWatcher);
- mInstrumentation.registerObserver(mWatcher);
- mWebInstantAppsDisabled.registerObserver(mWatcher);
- mAppsFilter.registerObserver(mWatcher);
- mInstantAppRegistry.registerObserver(mWatcher);
- mSettings.registerObserver(mWatcher);
- mIsolatedOwners.registerObserver(mWatcher);
- mComponentResolver.registerObserver(mWatcher);
- mFrozenPackages.registerObserver(mWatcher);
- // If neither "build" attribute is true then this may be a mockito test, and verification
- // can fail as a false positive.
- Watchable.verifyWatchedAttributes(this, mWatcher, !(mIsEngBuild || mIsUserDebugBuild));
+ private void registerObservers(boolean verify) {
+ // Null check to handle nullable test parameters
+ if (mPackages != null) {
+ mPackages.registerObserver(mWatcher);
+ }
+ if (mSharedLibraries != null) {
+ mSharedLibraries.registerObserver(mWatcher);
+ }
+ if (mInstrumentation != null) {
+ mInstrumentation.registerObserver(mWatcher);
+ }
+ if (mWebInstantAppsDisabled != null) {
+ mWebInstantAppsDisabled.registerObserver(mWatcher);
+ }
+ if (mAppsFilter != null) {
+ mAppsFilter.registerObserver(mWatcher);
+ }
+ if (mInstantAppRegistry != null) {
+ mInstantAppRegistry.registerObserver(mWatcher);
+ }
+ if (mSettings != null) {
+ mSettings.registerObserver(mWatcher);
+ }
+ if (mIsolatedOwners != null) {
+ mIsolatedOwners.registerObserver(mWatcher);
+ }
+ if (mComponentResolver != null) {
+ mComponentResolver.registerObserver(mWatcher);
+ }
+ if (mFrozenPackages != null) {
+ mFrozenPackages.registerObserver(mWatcher);
+ }
+ if (verify) {
+ // If neither "build" attribute is true then this may be a mockito test,
+ // and verification can fail as a false positive.
+ Watchable.verifyWatchedAttributes(this, mWatcher, !(mIsEngBuild || mIsUserDebugBuild));
+ }
}
/**
@@ -1619,7 +1611,6 @@
mInstallLock = injector.getInstallLock();
mLock = injector.getLock();
mPackageStateWriteLock = mLock;
- mLiveComputerSyncLock = mLock;
mPermissionManager = injector.getPermissionManagerServiceInternal();
mSettings = injector.getSettings();
mUserManager = injector.getUserManagerService();
@@ -1639,6 +1630,7 @@
mIncrementalManager = testParams.incrementalManager;
mInstallerService = testParams.installerService;
mInstantAppRegistry = testParams.instantAppRegistry;
+ mChangedPackagesTracker = testParams.changedPackagesTracker;
mInstantAppResolverConnection = testParams.instantAppResolverConnection;
mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
@@ -1678,10 +1670,6 @@
mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
mResolveComponentName = testParams.resolveComponentName;
- // Disable snapshots in this instance of PackageManagerService, which is only used
- // for testing. The instance still needs a live computer. The snapshot computer
- // is set to null since it must never be used by this instance.
- mSnapshotEnabled = false;
mLiveComputer = createLiveComputer();
mSnapshotComputer = null;
mSnapshotStatistics = null;
@@ -1707,13 +1695,13 @@
mSuspendPackageHelper = testParams.suspendPackageHelper;
mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
+ registerObservers(false);
invalidatePackageInfoCache();
}
public PackageManagerService(PackageManagerServiceInjector injector, boolean onlyCore,
boolean factoryTest, final String buildFingerprint, final boolean isEngBuild,
- final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion,
- boolean snapshotEnabled) {
+ final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion) {
mIsEngBuild = isEngBuild;
mIsUserDebugBuild = isUserDebugBuild;
mSdkVersion = sdkVersion;
@@ -1728,7 +1716,6 @@
mInjector.bootstrap(this);
mLock = injector.getLock();
mPackageStateWriteLock = mLock;
- mLiveComputerSyncLock = mLock;
mInstallLock = injector.getInstallLock();
LockGuard.installLock(mLock, LockGuard.INDEX_PACKAGES);
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
@@ -1832,7 +1819,10 @@
mApexManager = injector.getApexManager();
mAppsFilter = mInjector.getAppsFilter();
- mInstantAppRegistry = new InstantAppRegistry(this, mPermissionManager, mPmInternal);
+ mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
+ mInjector.getUserManagerInternal(), new DeletePackageHelper(this));
+
+ mChangedPackagesTracker = new ChangedPackagesTracker();
mAppInstallDir = new File(Environment.getDataDirectory(), "app");
@@ -1857,16 +1847,12 @@
synchronized (mLock) {
// Create the computer as soon as the state objects have been installed. The
// cached computer is the same as the live computer until the end of the
- // constructor, at which time the invalidation method updates it. The cache is
- // corked initially to ensure a cached computer is not built until the end of the
- // constructor.
+ // constructor, at which time the invalidation method updates it.
mSnapshotStatistics = new SnapshotStatistics();
- sSnapshotCorked.set(1);
sSnapshotInvalid.set(true);
mLiveComputer = createLiveComputer();
mSnapshotComputer = null;
- mSnapshotEnabled = snapshotEnabled;
- registerObserver();
+ registerObservers(true);
}
// CHECKSTYLE:OFF IndentationCheck
@@ -2161,7 +2147,7 @@
|| !ps.getUserStateOrDefault(userId).isInstalled()) {
continue;
}
- mInstantAppRegistry.addInstantAppLPw(userId, ps.getAppId());
+ mInstantAppRegistry.addInstantApp(userId, ps.getAppId());
}
}
@@ -2852,11 +2838,17 @@
// TODO: Implement
// 7. Consider installed instant apps unused longer than min cache period
- if (internalVolume && mInstantAppRegistry.pruneInstalledInstantApps(bytes,
- android.provider.Settings.Global.getLong(mContext.getContentResolver(),
- Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
- InstantAppRegistry.DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
- return;
+ if (internalVolume) {
+ if (executeWithConsistentComputerReturning(computer ->
+ mInstantAppRegistry.pruneInstalledInstantApps(computer, bytes,
+ android.provider.Settings.Global.getLong(
+ mContext.getContentResolver(),
+ Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
+ InstantAppRegistry
+ .DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD)))
+ ) {
+ return;
+ }
}
// 8. Consider cached app data (below quotas)
@@ -2873,11 +2865,17 @@
// TODO: Implement
// 10. Consider instant meta-data (uninstalled apps) older that min cache period
- if (internalVolume && mInstantAppRegistry.pruneUninstalledInstantApps(bytes,
- android.provider.Settings.Global.getLong(mContext.getContentResolver(),
- Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
- InstantAppRegistry.DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
- return;
+ if (internalVolume) {
+ if (executeWithConsistentComputerReturning(computer ->
+ mInstantAppRegistry.pruneUninstalledInstantApps(computer, bytes,
+ android.provider.Settings.Global.getLong(
+ mContext.getContentResolver(),
+ Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
+ InstantAppRegistry
+ .DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD)))
+ ) {
+ return;
+ }
}
// 11. Free storage service cache
@@ -3041,26 +3039,7 @@
@GuardedBy("mLock")
void updateSequenceNumberLP(PackageSetting pkgSetting, int[] userList) {
- for (int i = userList.length - 1; i >= 0; --i) {
- final int userId = userList[i];
- SparseArray<String> changedPackages = mChangedPackages.get(userId);
- if (changedPackages == null) {
- changedPackages = new SparseArray<>();
- mChangedPackages.put(userId, changedPackages);
- }
- Map<String, Integer> sequenceNumbers = mChangedPackagesSequenceNumbers.get(userId);
- if (sequenceNumbers == null) {
- sequenceNumbers = new HashMap<>();
- mChangedPackagesSequenceNumbers.put(userId, sequenceNumbers);
- }
- final Integer sequenceNumber = sequenceNumbers.get(pkgSetting.getPackageName());
- if (sequenceNumber != null) {
- changedPackages.remove(sequenceNumber);
- }
- changedPackages.put(mChangedPackagesSequenceNumber, pkgSetting.getPackageName());
- sequenceNumbers.put(pkgSetting.getPackageName(), mChangedPackagesSequenceNumber);
- }
- mChangedPackagesSequenceNumber++;
+ mChangedPackagesTracker.updateSequenceNumber(pkgSetting.getPackageName(), userList);
}
@Override
@@ -3073,30 +3052,21 @@
return null;
}
enforceCrossUserPermission(callingUid, userId, false, false, "getChangedPackages");
- synchronized (mLock) {
- if (sequenceNumber >= mChangedPackagesSequenceNumber) {
- return null;
- }
- final SparseArray<String> changedPackages = mChangedPackages.get(userId);
- if (changedPackages == null) {
- return null;
- }
- final List<String> packageNames =
- new ArrayList<>(mChangedPackagesSequenceNumber - sequenceNumber);
- for (int i = sequenceNumber; i < mChangedPackagesSequenceNumber; i++) {
- final String packageName = changedPackages.get(i);
- if (packageName != null) {
- // Filter out the changes if the calling package should not be able to see it.
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (shouldFilterApplication(ps, callingUid, userId)) {
- continue;
- }
- packageNames.add(packageName);
+ final ChangedPackages changedPackages = mChangedPackagesTracker.getChangedPackages(
+ sequenceNumber, userId);
+
+ if (changedPackages != null) {
+ final List<String> packageNames = changedPackages.getPackageNames();
+ for (int index = packageNames.size() - 1; index >= 0; index--) {
+ // Filter out the changes if the calling package should not be able to see it.
+ final PackageSetting ps = mSettings.getPackageLPr(packageNames.get(index));
+ if (shouldFilterApplication(ps, callingUid, userId)) {
+ packageNames.remove(index);
}
}
- return packageNames.isEmpty()
- ? null : new ChangedPackages(mChangedPackagesSequenceNumber, packageNames);
}
+
+ return changedPackages;
}
@Override
@@ -3143,8 +3113,8 @@
@Override
public String getPermissionControllerPackageName() {
final int callingUid = Binder.getCallingUid();
- if (mComputer.isPackageStateAvailableAndVisible(mRequiredPermissionControllerPackage,
- callingUid, UserHandle.getUserId(callingUid))) {
+ if (mComputer.getPackageStateFiltered(mRequiredPermissionControllerPackage,
+ callingUid, UserHandle.getUserId(callingUid)) != null) {
return mRequiredPermissionControllerPackage;
}
@@ -3533,12 +3503,11 @@
}
enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */,
false /* checkShell */, "getEphemeralApplications");
- synchronized (mLock) {
- List<InstantAppInfo> instantApps = mInstantAppRegistry
- .getInstantAppsLPr(userId);
- if (instantApps != null) {
- return new ParceledListSlice<>(instantApps);
- }
+
+ List<InstantAppInfo> instantApps = executeWithConsistentComputerReturning(computer ->
+ mInstantAppRegistry.getInstantApps(computer, userId));
+ if (instantApps != null) {
+ return new ParceledListSlice<>(instantApps);
}
return null;
}
@@ -3565,10 +3534,11 @@
if (!isCallerSameApp(packageName, Binder.getCallingUid())) {
return null;
}
- synchronized (mLock) {
- return mInstantAppRegistry.getInstantAppCookieLPw(
- packageName, userId);
+ PackageStateInternal packageState = getPackageStateInternal(packageName);
+ if (packageState == null || packageState.getPkg() == null) {
+ return null;
}
+ return mInstantAppRegistry.getInstantAppCookie(packageState.getPkg(), userId);
}
@Override
@@ -3582,10 +3552,13 @@
if (!isCallerSameApp(packageName, Binder.getCallingUid())) {
return false;
}
- synchronized (mLock) {
- return mInstantAppRegistry.setInstantAppCookieLPw(
- packageName, cookie, userId);
+
+ PackageStateInternal packageState = getPackageStateInternal(packageName);
+ if (packageState == null || packageState.getPkg() == null) {
+ return false;
}
+ return mInstantAppRegistry.setInstantAppCookie(packageState.getPkg(), cookie,
+ mContext.getPackageManager().getInstantAppCookieMaxBytes(), userId);
}
@Override
@@ -3601,10 +3574,7 @@
enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */,
false /* checkShell */, "getInstantAppIcon");
- synchronized (mLock) {
- return mInstantAppRegistry.getInstantAppIconLPw(
- packageName, userId);
- }
+ return mInstantAppRegistry.getInstantAppIcon(packageName, userId);
}
boolean isCallerSameApp(String packageName, int uid) {
@@ -3722,16 +3692,14 @@
}
}
if (doTrim) {
- final boolean dexOptDialogShown;
- synchronized (mLock) {
- dexOptDialogShown = mDexOptHelper.isDexOptDialogShown();
- }
- if (!isFirstBoot() && dexOptDialogShown) {
- try {
- ActivityManager.getService().showBootMessage(
- mContext.getResources().getString(
- R.string.android_upgrading_fstrim), true);
- } catch (RemoteException e) {
+ if (!isFirstBoot()) {
+ if (mDexOptHelper.isDexOptDialogShown()) {
+ try {
+ ActivityManager.getService().showBootMessage(
+ mContext.getResources().getString(
+ R.string.android_upgrading_fstrim), true);
+ } catch (RemoteException e) {
+ }
}
}
sm.runMaintenance();
@@ -3751,29 +3719,27 @@
@Override
public void notifyPackageUse(String packageName, int reason) {
- synchronized (mLock) {
- final int callingUid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(callingUid);
+ final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ boolean notify = executeWithConsistentComputerReturning(computer -> {
if (getInstantAppPackageName(callingUid) != null) {
- if (!isCallerSameApp(packageName, callingUid)) {
- return;
- }
+ return isCallerSameApp(packageName, callingUid);
} else {
- if (isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID)) {
- return;
- }
+ return !isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID);
}
- notifyPackageUseLocked(packageName, reason);
- }
- }
-
- @GuardedBy("mLock")
- private void notifyPackageUseLocked(String packageName, int reason) {
- final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
- if (pkgSetting == null) {
+ });
+ if (!notify) {
return;
}
- pkgSetting.getPkgState().setLastPackageUsageTimeInMills(reason, System.currentTimeMillis());
+
+ notifyPackageUseInternal(packageName, reason);
+ }
+
+ private void notifyPackageUseInternal(String packageName, int reason) {
+ long time = System.currentTimeMillis();
+ commitPackageStateMutation(null, packageName, packageState -> {
+ packageState.setLastPackageUsageTime(reason, time);
+ });
}
@Override
@@ -3899,10 +3865,12 @@
// This is the last chance to write out pending restriction settings
if (mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) {
mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
- for (int userId : mDirtyUsers) {
- mSettings.writePackageRestrictionsLPr(userId);
+ synchronized (mDirtyUsers) {
+ for (int userId : mDirtyUsers) {
+ mSettings.writePackageRestrictionsLPr(userId);
+ }
+ mDirtyUsers.clear();
}
- mDirtyUsers.clear();
}
}
}
@@ -3918,12 +3886,9 @@
throw new SecurityException("dumpProfiles");
}
- AndroidPackage pkg;
- synchronized (mLock) {
- pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
+ AndroidPackage pkg = getPackage(packageName);
+ if (pkg == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
}
synchronized (mInstallLock) {
@@ -3946,14 +3911,12 @@
public Property getProperty(String propertyName, String packageName, String className) {
Objects.requireNonNull(propertyName);
Objects.requireNonNull(packageName);
- synchronized (mLock) {
- final PackageStateInternal ps = getPackageStateInternal(packageName);
- if (shouldFilterApplication(ps, Binder.getCallingUid(),
- UserHandle.getCallingUserId())) {
- return null;
- }
- return mPackageProperty.getProperty(propertyName, packageName, className);
+ PackageStateInternal packageState = mComputer.getPackageStateFiltered(packageName,
+ Binder.getCallingUid(), UserHandle.getCallingUserId());
+ if (packageState == null) {
+ return null;
}
+ return mPackageProperty.getProperty(propertyName, packageName, className);
}
@Override
@@ -4038,66 +4001,33 @@
@Override
public void notifyPackageAdded(String packageName, int uid) {
- final PackageListObserver[] observers;
- synchronized (mLock) {
- if (mPackageListObservers.size() == 0) {
- return;
- }
- final PackageListObserver[] observerArray =
- new PackageListObserver[mPackageListObservers.size()];
- observers = mPackageListObservers.toArray(observerArray);
- }
- for (int i = observers.length - 1; i >= 0; --i) {
- observers[i].onPackageAdded(packageName, uid);
- }
+ mPackageObserverHelper.notifyAdded(packageName, uid);
}
@Override
public void notifyPackageChanged(String packageName, int uid) {
- final PackageListObserver[] observers;
- synchronized (mLock) {
- if (mPackageListObservers.size() == 0) {
- return;
- }
- final PackageListObserver[] observerArray =
- new PackageListObserver[mPackageListObservers.size()];
- observers = mPackageListObservers.toArray(observerArray);
- }
- for (int i = observers.length - 1; i >= 0; --i) {
- observers[i].onPackageChanged(packageName, uid);
- }
+ mPackageObserverHelper.notifyChanged(packageName, uid);
}
@Override
public void notifyPackageRemoved(String packageName, int uid) {
- final PackageListObserver[] observers;
- synchronized (mLock) {
- if (mPackageListObservers.size() == 0) {
- return;
- }
- final PackageListObserver[] observerArray =
- new PackageListObserver[mPackageListObservers.size()];
- observers = mPackageListObservers.toArray(observerArray);
- }
- for (int i = observers.length - 1; i >= 0; --i) {
- observers[i].onPackageRemoved(packageName, uid);
- }
+ mPackageObserverHelper.notifyRemoved(packageName, uid);
}
- void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting,
+ void sendPackageAddedForUser(String packageName, @NonNull PackageStateInternal packageState,
int userId, int dataLoaderType) {
- final boolean isSystem = PackageManagerServiceUtils.isSystemApp(pkgSetting)
- || PackageManagerServiceUtils.isUpdatedSystemApp(pkgSetting);
- final boolean isInstantApp = pkgSetting.getInstantApp(userId);
+ final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
+ final boolean isSystem = packageState.isSystem();
+ final boolean isInstantApp = userState.isInstantApp();
final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId };
final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
sendPackageAddedForNewUsers(packageName, isSystem /*sendBootCompleted*/,
- false /*startReceiver*/, pkgSetting.getAppId(), userIds, instantUserIds,
+ false /*startReceiver*/, packageState.getAppId(), userIds, instantUserIds,
dataLoaderType);
// Send a session commit broadcast
final PackageInstaller.SessionInfo info = new PackageInstaller.SessionInfo();
- info.installReason = pkgSetting.getInstallReason(userId);
+ info.installReason = userState.getInstallReason();
info.appPackageName = packageName;
sendSessionCommitBroadcast(info, userId);
}
@@ -4129,7 +4059,6 @@
public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden,
int userId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
- PackageSetting pkgSetting;
final int callingUid = Binder.getCallingUid();
enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
true /* checkShell */, "setApplicationHiddenSetting for user " + userId);
@@ -4139,74 +4068,70 @@
return false;
}
+ // Do not allow "android" is being disabled
+ if ("android".equals(packageName)) {
+ Slog.w(TAG, "Cannot hide package: android");
+ return false;
+ }
+
final long callingId = Binder.clearCallingIdentity();
try {
- boolean sendAdded = false;
- boolean sendRemoved = false;
- // writer
- synchronized (mLock) {
- pkgSetting = mSettings.getPackageLPr(packageName);
- if (pkgSetting == null) {
- return false;
- }
- if (shouldFilterApplication(pkgSetting, callingUid, userId)) {
- return false;
- }
- // Do not allow "android" is being disabled
- if ("android".equals(packageName)) {
- Slog.w(TAG, "Cannot hide package: android");
- return false;
- }
- AndroidPackage pkg = mPackages.get(packageName);
- if (pkg != null) {
- // Cannot hide SDK libs as they are controlled by SDK manager.
- if (pkg.getSdkLibName() != null) {
- Slog.w(TAG, "Cannot hide package: " + packageName
- + " providing SDK library: "
- + pkg.getSdkLibName());
- return false;
- }
- // Cannot hide static shared libs as they are considered
- // a part of the using app (emulating static linking). Also
- // static libs are installed always on internal storage.
- if (pkg.getStaticSharedLibName() != null) {
- Slog.w(TAG, "Cannot hide package: " + packageName
- + " providing static shared library: "
- + pkg.getStaticSharedLibName());
- return false;
- }
- }
- // Only allow protected packages to hide themselves.
- if (hidden && !UserHandle.isSameApp(callingUid, pkgSetting.getAppId())
- && mProtectedPackages.isPackageStateProtected(userId, packageName)) {
- Slog.w(TAG, "Not hiding protected package: " + packageName);
- return false;
- }
+ final PackageStateInternal packageState =
+ mComputer.getPackageStateFiltered(packageName, callingUid, userId);
+ if (packageState == null) {
+ return false;
+ }
- if (pkgSetting.getHidden(userId) != hidden) {
- pkgSetting.setHidden(hidden, userId);
- mSettings.writePackageRestrictionsLPr(userId);
- if (hidden) {
- sendRemoved = true;
- } else {
- sendAdded = true;
- }
+ // Cannot hide static shared libs as they are considered
+ // a part of the using app (emulating static linking). Also
+ // static libs are installed always on internal storage.
+ AndroidPackage pkg = packageState.getPkg();
+ if (pkg != null) {
+ // Cannot hide SDK libs as they are controlled by SDK manager.
+ if (pkg.getSdkLibName() != null) {
+ Slog.w(TAG, "Cannot hide package: " + packageName
+ + " providing SDK library: "
+ + pkg.getSdkLibName());
+ return false;
+ }
+ // Cannot hide static shared libs as they are considered
+ // a part of the using app (emulating static linking). Also
+ // static libs are installed always on internal storage.
+ if (pkg.getStaticSharedLibName() != null) {
+ Slog.w(TAG, "Cannot hide package: " + packageName
+ + " providing static shared library: "
+ + pkg.getStaticSharedLibName());
+ return false;
}
}
- if (sendAdded) {
- sendPackageAddedForUser(packageName, pkgSetting, userId, DataLoaderType.NONE);
- return true;
+ // Only allow protected packages to hide themselves.
+ if (hidden && !UserHandle.isSameApp(callingUid, packageState.getAppId())
+ && mProtectedPackages.isPackageStateProtected(userId, packageName)) {
+ Slog.w(TAG, "Not hiding protected package: " + packageName);
+ return false;
}
- if (sendRemoved) {
- killApplication(packageName, pkgSetting.getAppId(), userId,
- "hiding pkg");
- sendApplicationHiddenForUser(packageName, pkgSetting, userId);
- return true;
+
+ if (packageState.getUserStateOrDefault(userId).isHidden() == hidden) {
+ return false;
}
+
+ commitPackageStateMutation(null, packageName, packageState1 ->
+ packageState1.userState(userId).setHidden(hidden));
+
+ final PackageStateInternal newPackageState = getPackageStateInternal(packageName);
+
+ if (hidden) {
+ killApplication(packageName, newPackageState.getAppId(), userId, "hiding pkg");
+ sendApplicationHiddenForUser(packageName, newPackageState, userId);
+ } else {
+ sendPackageAddedForUser(packageName, newPackageState, userId, DataLoaderType.NONE);
+ }
+
+ scheduleWritePackageRestrictions(userId);
+ return true;
} finally {
Binder.restoreCallingIdentity(callingId);
}
- return false;
}
@Override
@@ -4219,21 +4144,20 @@
"setSystemAppHiddenUntilInstalled");
}
- synchronized (mLock) {
- final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
- if (pkgSetting == null || !pkgSetting.isSystem()) {
- return;
- }
- if (pkgSetting.getPkg().isCoreApp() && !calledFromSystemOrPhone) {
- throw new SecurityException("Only system or phone callers can modify core apps");
- }
- pkgSetting.getPkgState().setHiddenUntilInstalled(hidden);
- final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(packageName);
- if (disabledPs == null) {
- return;
- }
- disabledPs.getPkgState().setHiddenUntilInstalled(hidden);
+ final PackageStateInternal stateRead = getPackageStateInternal(packageName);
+ if (stateRead == null || !stateRead.isSystem() || stateRead.getPkg() == null) {
+ return;
}
+ if (stateRead.getPkg().isCoreApp() && !calledFromSystemOrPhone) {
+ throw new SecurityException("Only system or phone callers can modify core apps");
+ }
+
+ commitPackageStateMutation(null, mutator -> {
+ mutator.forPackage(packageName)
+ .setHiddenUntilInstalled(hidden);
+ mutator.forDisabledSystemPackage(packageName)
+ .setHiddenUntilInstalled(hidden);
+ });
}
@Override
@@ -4246,19 +4170,17 @@
"setSystemAppHiddenUntilInstalled");
}
- synchronized (mLock) {
- final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
- // The target app should always be in system
- if (pkgSetting == null || !pkgSetting.isSystem()) {
- return false;
- }
- if (pkgSetting.getPkg().isCoreApp() && !calledFromSystemOrPhone) {
- throw new SecurityException("Only system or phone callers can modify core apps");
- }
- // Check if the install state is the same
- if (pkgSetting.getInstalled(userId) == installed) {
- return false;
- }
+ final PackageStateInternal packageState = getPackageStateInternal(packageName);
+ // The target app should always be in system
+ if (packageState == null || !packageState.isSystem() || packageState.getPkg() == null) {
+ return false;
+ }
+ if (packageState.getPkg().isCoreApp() && !calledFromSystemOrPhone) {
+ throw new SecurityException("Only system or phone callers can modify core apps");
+ }
+ // Check if the install state is the same
+ if (packageState.getUserStateOrDefault(userId).isInstalled() == installed) {
+ return false;
}
final long callingId = Binder.clearCallingIdentity();
@@ -4286,14 +4208,14 @@
}
}
- private void sendApplicationHiddenForUser(String packageName, PackageSetting pkgSetting,
+ private void sendApplicationHiddenForUser(String packageName, PackageStateInternal packageState,
int userId) {
final PackageRemovedInfo info = new PackageRemovedInfo(this);
info.mRemovedPackage = packageName;
- info.mInstallerPackageName = pkgSetting.getInstallSource().installerPackageName;
+ info.mInstallerPackageName = packageState.getInstallSource().installerPackageName;
info.mRemovedUsers = new int[] {userId};
info.mBroadcastUsers = new int[] {userId};
- info.mUid = UserHandle.getUid(userId, pkgSetting.getAppId());
+ info.mUid = UserHandle.getUid(userId, packageState.getAppId());
info.sendPackageRemovedBroadcasts(true /*killApp*/, false /*removedBySystem*/);
}
@@ -4348,45 +4270,52 @@
final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
final IntArray changedUids = new IntArray(packageNames.length);
final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
- final boolean[] canRestrict = (restrictionFlags != 0)
- ? mSuspendPackageHelper.canSuspendPackageForUser(packageNames, userId, callingUid)
- : null;
- for (int i = 0; i < packageNames.length; i++) {
- final String packageName = packageNames[i];
- final PackageSetting pkgSetting;
- synchronized (mLock) {
- pkgSetting = mSettings.getPackageLPr(packageName);
- if (pkgSetting == null
- || shouldFilterApplication(pkgSetting, callingUid, userId)) {
+ ArraySet<String> changesToCommit = new ArraySet<>();
+ executeWithConsistentComputer(computer -> {
+ final boolean[] canRestrict = (restrictionFlags != 0)
+ ? mSuspendPackageHelper.canSuspendPackageForUser(computer, packageNames, userId,
+ callingUid) : null;
+ for (int i = 0; i < packageNames.length; i++) {
+ final String packageName = packageNames[i];
+ final PackageStateInternal packageState =
+ computer.getPackageStateInternal(packageName);
+ if (packageState == null
+ || shouldFilterApplication(packageState, callingUid, userId)) {
Slog.w(TAG, "Could not find package setting for package: " + packageName
+ ". Skipping...");
unactionedPackages.add(packageName);
continue;
}
- }
- if (canRestrict != null && !canRestrict[i]) {
- unactionedPackages.add(packageName);
- continue;
- }
- synchronized (mLock) {
- final int oldDistractionFlags = pkgSetting.getDistractionFlags(userId);
+ if (canRestrict != null && !canRestrict[i]) {
+ unactionedPackages.add(packageName);
+ continue;
+ }
+ final int oldDistractionFlags = packageState.getUserStateOrDefault(userId)
+ .getDistractionFlags();
if (restrictionFlags != oldDistractionFlags) {
- pkgSetting.setDistractionFlags(restrictionFlags, userId);
changedPackagesList.add(packageName);
- changedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
+ changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ changesToCommit.add(packageName);
}
}
- }
+ });
+
+ commitPackageStateMutation(null, mutator -> {
+ final int size = changesToCommit.size();
+ for (int index = 0; index < size; index++) {
+ mutator.forPackage(changesToCommit.valueAt(index))
+ .userState(userId)
+ .setDistractionFlags(restrictionFlags);
+ }
+ });
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(
new String[changedPackagesList.size()]);
mHandler.post(() -> mBroadcastHelper.sendDistractingPackagesChanged(
changedPackages, changedUids.toArray(), userId, restrictionFlags));
- synchronized (mLock) {
- scheduleWritePackageRestrictionsLocked(userId);
- }
+ scheduleWritePackageRestrictions(userId);
}
return unactionedPackages.toArray(new String[0]);
}
@@ -4450,10 +4379,9 @@
}
void unsuspendForSuspendingPackage(String suspendingPackage, int userId) {
- final String[] allPackages;
- synchronized (mLock) {
- allPackages = mPackages.keySet().toArray(new String[mPackages.size()]);
- }
+ // TODO: This can be replaced by a special parameter to iterate all packages, rather than
+ // this weird pre-collect of all packages.
+ final String[] allPackages = getPackageStates().keySet().toArray(new String[0]);
mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(
allPackages, suspendingPackage::equals, userId);
}
@@ -4479,22 +4407,27 @@
private void removeDistractingPackageRestrictions(String[] packagesToChange, int userId) {
final List<String> changedPackages = new ArrayList<>();
final IntArray changedUids = new IntArray();
- synchronized (mLock) {
- for (String packageName : packagesToChange) {
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps != null && ps.getDistractionFlags(userId) != 0) {
- ps.setDistractionFlags(0, userId);
- changedPackages.add(ps.getPackageName());
- changedUids.add(UserHandle.getUid(userId, ps.getAppId()));
- }
+ for (String packageName : packagesToChange) {
+ final PackageStateInternal ps = getPackageStateInternal(packageName);
+ if (ps != null && ps.getUserStateOrDefault(userId).getDistractionFlags() != 0) {
+ changedPackages.add(ps.getPackageName());
+ changedUids.add(UserHandle.getUid(userId, ps.getAppId()));
}
- if (!changedPackages.isEmpty()) {
- final String[] packageArray = changedPackages.toArray(
- new String[changedPackages.size()]);
- mHandler.post(() -> mBroadcastHelper.sendDistractingPackagesChanged(
- packageArray, changedUids.toArray(), userId, 0));
- scheduleWritePackageRestrictionsLocked(userId);
+ }
+ commitPackageStateMutation(null, mutator -> {
+ for (int index = 0; index < changedPackages.size(); index++) {
+ mutator.forPackage(changedPackages.get(index))
+ .userState(userId)
+ .setDistractionFlags(0);
}
+ });
+
+ if (!changedPackages.isEmpty()) {
+ final String[] packageArray = changedPackages.toArray(
+ new String[changedPackages.size()]);
+ mHandler.post(() -> mBroadcastHelper.sendDistractingPackagesChanged(
+ packageArray, changedUids.toArray(), userId, 0));
+ scheduleWritePackageRestrictions(userId);
}
}
@@ -4616,43 +4549,40 @@
public void setInstallerPackageName(String targetPackage, String installerPackageName) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
- if (getInstantAppPackageName(callingUid) != null) {
- return;
- }
- // writer
- synchronized (mLock) {
- PackageSetting targetPackageSetting = mSettings.getPackageLPr(targetPackage);
- if (targetPackageSetting == null
- || shouldFilterApplication(
- targetPackageSetting, callingUid, callingUserId)) {
+ final FunctionalUtils.ThrowingCheckedFunction<Computer, Boolean, RuntimeException>
+ implementation = computer -> {
+ if (computer.getInstantAppPackageName(callingUid) != null) {
+ return false;
+ }
+
+ PackageStateInternal targetPackageState =
+ computer.getPackageStateInternal(targetPackage);
+ if (targetPackageState == null
+ || computer.shouldFilterApplication(targetPackageState, callingUid,
+ callingUserId)) {
throw new IllegalArgumentException("Unknown target package: " + targetPackage);
}
- PackageSetting installerPackageSetting;
+ PackageStateInternal installerPackageState = null;
if (installerPackageName != null) {
- installerPackageSetting = mSettings.getPackageLPr(installerPackageName);
- if (installerPackageSetting == null
+ installerPackageState = computer.getPackageStateInternal(installerPackageName);
+ if (installerPackageState == null
|| shouldFilterApplication(
- installerPackageSetting, callingUid, callingUserId)) {
+ installerPackageState, callingUid, callingUserId)) {
throw new IllegalArgumentException("Unknown installer package: "
+ installerPackageName);
}
- } else {
- installerPackageSetting = null;
}
Signature[] callerSignature;
final int appId = UserHandle.getAppId(callingUid);
- final Object obj = mSettings.getSettingLPr(appId);
- if (obj != null) {
- if (obj instanceof SharedUserSetting) {
- callerSignature =
- ((SharedUserSetting) obj).signatures.mSigningDetails.getSignatures();
- } else if (obj instanceof PackageSetting) {
- callerSignature =
- ((PackageSetting) obj).getSigningDetails().getSignatures();
+ Pair<PackageStateInternal, SharedUserApi> either =
+ computer.getPackageOrSharedUser(appId);
+ if (either != null) {
+ if (either.first != null) {
+ callerSignature = either.first.getSigningDetails().getSignatures();
} else {
- throw new SecurityException("Bad object " + obj + " for uid " + callingUid);
+ callerSignature = either.second.getSigningDetails().getSignatures();
}
} else {
throw new SecurityException("Unknown calling UID: " + callingUid);
@@ -4660,22 +4590,22 @@
// Verify: can't set installerPackageName to a package that is
// not signed with the same cert as the caller.
- if (installerPackageSetting != null) {
+ if (installerPackageState != null) {
if (compareSignatures(callerSignature,
- installerPackageSetting.getSigningDetails().getSignatures())
+ installerPackageState.getSigningDetails().getSignatures())
!= PackageManager.SIGNATURE_MATCH) {
throw new SecurityException(
"Caller does not have same cert as new installer package "
- + installerPackageName);
+ + installerPackageName);
}
}
// Verify: if target already has an installer package, it must
// be signed with the same cert as the caller.
String targetInstallerPackageName =
- targetPackageSetting.getInstallSource().installerPackageName;
- PackageSetting targetInstallerPkgSetting = targetInstallerPackageName == null ? null :
- mSettings.getPackageLPr(targetInstallerPackageName);
+ targetPackageState.getInstallSource().installerPackageName;
+ PackageStateInternal targetInstallerPkgSetting = targetInstallerPackageName == null
+ ? null : computer.getPackageStateInternal(targetInstallerPackageName);
if (targetInstallerPkgSetting != null) {
if (compareSignatures(callerSignature,
@@ -4683,10 +4613,10 @@
!= PackageManager.SIGNATURE_MATCH) {
throw new SecurityException(
"Caller does not have same cert as old installer package "
- + targetInstallerPackageName);
+ + targetInstallerPackageName);
}
} else if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
- != PackageManager.PERMISSION_GRANTED) {
+ != PERMISSION_GRANTED) {
// This is probably an attempt to exploit vulnerability b/150857253 of taking
// privileged installer permissions when the installer has been uninstalled or
// was never set.
@@ -4702,17 +4632,39 @@
+ Manifest.permission.INSTALL_PACKAGES);
} else {
// If change disabled, fail silently for backwards compatibility
- return;
+ return false;
}
} finally {
Binder.restoreCallingIdentity(binderToken);
}
}
- // Okay!
- targetPackageSetting.setInstallerPackageName(installerPackageName);
- mSettings.addInstallerPackageNames(targetPackageSetting.getInstallSource());
- mAppsFilter.addPackage(targetPackageSetting);
+ return true;
+ };
+ PackageStateMutator.InitialState initialState = recordInitialState();
+ boolean allowed = executeWithConsistentComputerReturningThrowing(implementation);
+ if (allowed) {
+ // TODO: Need to lock around here to handle mSettings.addInstallerPackageNames,
+ // should find an alternative which avoids any race conditions
+ PackageStateInternal targetPackageState;
+ synchronized (mLock) {
+ PackageStateMutator.Result result = commitPackageStateMutation(initialState,
+ targetPackage, state -> state.setInstaller(installerPackageName));
+ if (result.isPackagesChanged() || result.isStateChanged()) {
+ synchronized (mPackageStateWriteLock) {
+ allowed = executeWithConsistentComputerReturningThrowing(implementation);
+ if (allowed) {
+ commitPackageStateMutation(null, targetPackage,
+ state -> state.setInstaller(installerPackageName));
+ } else {
+ return;
+ }
+ }
+ }
+ targetPackageState = getPackageStateInternal(targetPackage);
+ mSettings.addInstallerPackageNames(targetPackageState.getInstallSource());
+ }
+ mAppsFilter.addPackage(targetPackageState);
scheduleWriteSettings();
}
}
@@ -4725,21 +4677,42 @@
}
mInjector.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(),
callerPackageName);
- synchronized (mLock) {
- PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps == null || shouldFilterApplication(
- ps, Binder.getCallingUid(), UserHandle.getCallingUserId())) {
+
+ final PackageStateMutator.InitialState initialState = recordInitialState();
+
+ final FunctionalUtils.ThrowingFunction<Computer, PackageStateMutator.Result>
+ implementation = computer -> {
+ PackageStateInternal packageState = computer.getPackageStateFiltered(packageName,
+ Binder.getCallingUid(), UserHandle.getCallingUserId());
+ if (packageState == null) {
throw new IllegalArgumentException("Unknown target package " + packageName);
}
- if (!Objects.equals(callerPackageName, ps.getInstallSource().installerPackageName)) {
+
+ if (!Objects.equals(callerPackageName,
+ packageState.getInstallSource().installerPackageName)) {
throw new IllegalArgumentException("Calling package " + callerPackageName
+ " is not installer for " + packageName);
}
- if (ps.getCategoryOverride() != categoryHint) {
- ps.setCategoryOverride(categoryHint);
- scheduleWriteSettings();
+ if (packageState.getCategoryOverride() != categoryHint) {
+ return commitPackageStateMutation(initialState,
+ packageName, state -> state.setCategoryOverride(categoryHint));
+ } else {
+ return null;
}
+ };
+
+ PackageStateMutator.Result result = executeWithConsistentComputerReturning(implementation);
+ if (result != null && result.isStateChanged() && !result.isSpecificPackageNull()) {
+ // TODO: Specific return value of what state changed?
+ // The installer on record might have changed, retry with lock
+ synchronized (mPackageStateWriteLock) {
+ result = executeWithConsistentComputerReturning(implementation);
+ }
+ }
+
+ if (result != null && result.isCommitted()) {
+ scheduleWriteSettings();
}
}
@@ -4792,32 +4765,6 @@
});
}
- // Stop gap method to allow mutating a package setting before commit on write is added
- private void mutateInstalledPackageSetting(@NonNull String packageName, int callingUid,
- @UserIdInt int userId,
- @NonNull FunctionalUtils.ThrowingConsumer<PackageSetting> consumerLocked) {
- synchronized (mLock) {
- PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps == null) {
- Slog.w(TAG, "Failed to get package setting. Package " + packageName
- + " is not installed");
- return;
- }
- if (!ps.getInstalled(userId)) {
- Slog.w(TAG, "Failed to get package setting. Package " + packageName
- + " is not installed for user " + userId);
- return;
- }
- if (shouldFilterApplication(ps, callingUid, userId)) {
- Slog.w(TAG, "Failed to get package setting. Package " + packageName
- + " is not visible to the calling app");
- return;
- }
-
- consumerLocked.accept(ps);
- }
- }
-
void notifyPackageChangeObservers(PackageChangeEvent event) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "notifyPackageChangeObservers");
@@ -4872,9 +4819,8 @@
return mComputer.resolveExternalPackageName(pkg);
}
- @GuardedBy("mLock")
- String resolveInternalPackageNameLPr(String packageName, long versionCode) {
- return mComputer.resolveInternalPackageNameLPr(packageName, versionCode);
+ String resolveInternalPackageName(String packageName, long versionCode) {
+ return mComputer.resolveInternalPackageName(packageName, versionCode);
}
boolean isCallerVerifier(int callingUid) {
@@ -4947,28 +4893,29 @@
int userId) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DELETE_PACKAGES, null);
- // TODO (b/157774108): This should fail on non-existent packages.
- synchronized (mLock) {
- AndroidPackage pkg = mPackages.get(packageName);
- if (pkg != null) {
- // Cannot block uninstall SDK libs as they are controlled by SDK manager.
- if (pkg.getSdkLibName() != null) {
- Slog.w(TAG, "Cannot block uninstall of package: " + packageName
- + " providing SDK library: " + pkg.getSdkLibName());
- return false;
- }
- // Cannot block uninstall of static shared libs as they are
- // considered a part of the using app (emulating static linking).
- // Also static libs are installed always on internal storage.
- if (pkg.getStaticSharedLibName() != null) {
- Slog.w(TAG, "Cannot block uninstall of package: " + packageName
- + " providing static shared library: " + pkg.getStaticSharedLibName());
- return false;
- }
+ PackageStateInternal packageState = getPackageStateInternal(packageName);
+ if (packageState != null && packageState.getPkg() != null) {
+ AndroidPackage pkg = packageState.getPkg();
+ // Cannot block uninstall SDK libs as they are controlled by SDK manager.
+ if (pkg.getSdkLibName() != null) {
+ Slog.w(TAG, "Cannot block uninstall of package: " + packageName
+ + " providing SDK library: " + pkg.getSdkLibName());
+ return false;
}
- mSettings.setBlockUninstallLPw(userId, packageName, blockUninstall);
- mSettings.writePackageRestrictionsLPr(userId);
+ // Cannot block uninstall of static shared libs as they are
+ // considered a part of the using app (emulating static linking).
+ // Also static libs are installed always on internal storage.
+ if (pkg.getStaticSharedLibName() != null) {
+ Slog.w(TAG, "Cannot block uninstall of package: " + packageName
+ + " providing static shared library: " + pkg.getStaticSharedLibName());
+ return false;
+ }
}
+ synchronized (mLock) {
+ mSettings.setBlockUninstallLPw(userId, packageName, blockUninstall);
+ }
+
+ scheduleWritePackageRestrictions(userId);
return true;
}
@@ -4978,24 +4925,17 @@
}
@Override
- public boolean setRequiredForSystemUser(String packageName, boolean systemUserApp) {
+ public boolean setRequiredForSystemUser(String packageName, boolean requiredForSystemUser) {
PackageManagerServiceUtils.enforceSystemOrRoot(
"setRequiredForSystemUser can only be run by the system or root");
- synchronized (mLock) {
- PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps == null) {
- Log.w(TAG, "Package doesn't exist: " + packageName);
- return false;
- }
- if (systemUserApp) {
- ps.setPrivateFlags(ps.getPrivateFlags()
- | ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER);
- } else {
- ps.setPrivateFlags(ps.getPrivateFlags()
- & ~ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER);
- }
- writeSettingsLPrTEMP();
+
+ PackageStateMutator.Result result = commitPackageStateMutation(null, packageName,
+ packageState -> packageState.setRequiredForSystemUser(requiredForSystemUser));
+ if (!result.isCommitted()) {
+ return false;
}
+
+ scheduleWriteSettings();
return true;
}
@@ -5004,12 +4944,8 @@
PackageManagerServiceUtils.enforceSystemOrRoot(
"Only the system can clear all profile data");
- final AndroidPackage pkg;
- synchronized (mLock) {
- pkg = mPackages.get(packageName);
- }
-
- try (PackageFreezer freezer = freezePackage(packageName, "clearApplicationProfileData")) {
+ final AndroidPackage pkg = getPackage(packageName);
+ try (PackageFreezer ignored = freezePackage(packageName, "clearApplicationProfileData")) {
synchronized (mInstallLock) {
mAppDataHelper.clearAppProfilesLIF(pkg);
}
@@ -5026,50 +4962,53 @@
enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
false /* checkShell */, "clear application data");
- final boolean filterApp;
- synchronized (mLock) {
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- filterApp = shouldFilterApplication(ps, callingUid, userId);
+ if (mComputer.getPackageStateFiltered(packageName, callingUid, userId) == null) {
+ if (observer != null) {
+ mHandler.post(() -> {
+ try {
+ observer.onRemoveCompleted(packageName, false);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Observer no longer exists.");
+ }
+ });
+ }
+ return;
}
- if (!filterApp && mProtectedPackages.isPackageDataProtected(userId, packageName)) {
+ if (mProtectedPackages.isPackageDataProtected(userId, packageName)) {
throw new SecurityException("Cannot clear data for a protected package: "
+ packageName);
}
+
// Queue up an async operation since the package deletion may take a little while.
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
final boolean succeeded;
- if (!filterApp) {
- try (PackageFreezer freezer = freezePackage(packageName,
- "clearApplicationUserData")) {
- synchronized (mInstallLock) {
- succeeded = clearApplicationUserDataLIF(packageName, userId);
- }
- synchronized (mLock) {
- mInstantAppRegistry.deleteInstantApplicationMetadataLPw(
- packageName, userId);
- if (succeeded) {
- resetComponentEnabledSettingsIfNeededLPw(packageName, userId);
- }
+ try (PackageFreezer freezer = freezePackage(packageName,
+ "clearApplicationUserData")) {
+ synchronized (mInstallLock) {
+ succeeded = clearApplicationUserDataLIF(packageName, userId);
+ }
+ mInstantAppRegistry.deleteInstantApplicationMetadata(packageName, userId);
+ synchronized (mLock) {
+ if (succeeded) {
+ resetComponentEnabledSettingsIfNeededLPw(packageName, userId);
}
}
- if (succeeded) {
- // invoke DeviceStorageMonitor's update method to clear any notifications
- DeviceStorageMonitorInternal dsm = LocalServices
- .getService(DeviceStorageMonitorInternal.class);
- if (dsm != null) {
- dsm.checkMemory();
- }
- if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId)
- == PERMISSION_GRANTED) {
- unsuspendForSuspendingPackage(packageName, userId);
- removeAllDistractingPackageRestrictions(userId);
- flushPackageRestrictionsAsUserInternalLocked(userId);
- }
+ }
+ if (succeeded) {
+ // invoke DeviceStorageMonitor's update method to clear any notifications
+ DeviceStorageMonitorInternal dsm = LocalServices
+ .getService(DeviceStorageMonitorInternal.class);
+ if (dsm != null) {
+ dsm.checkMemory();
}
- } else {
- succeeded = false;
+ if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId)
+ == PERMISSION_GRANTED) {
+ unsuspendForSuspendingPackage(packageName, userId);
+ removeAllDistractingPackageRestrictions(userId);
+ flushPackageRestrictionsAsUserInternalLocked(userId);
+ }
}
if (observer != null) {
try {
@@ -5089,17 +5028,7 @@
}
// Try finding details about the requested package
- AndroidPackage pkg;
- PackageSetting ps;
- synchronized (mLock) {
- pkg = mPackages.get(packageName);
- ps = mSettings.getPackageLPr(packageName);
- if (pkg == null) {
- if (ps != null) {
- pkg = ps.getPkg();
- }
- }
- }
+ AndroidPackage pkg = getPackage(packageName);
if (pkg == null) {
Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
return false;
@@ -5122,7 +5051,8 @@
} else {
flags = 0;
}
- mAppDataHelper.prepareAppDataContentsLIF(pkg, ps, userId, flags);
+ mAppDataHelper.prepareAppDataContentsLIF(pkg, getPackageStateInternal(packageName), userId,
+ flags);
return true;
}
@@ -5165,7 +5095,7 @@
updateSequenceNumberLP(pkgSetting, new int[] { userId });
updateInstantAppInstallerLocked(packageName);
- scheduleWritePackageRestrictionsLocked(userId);
+ scheduleWritePackageRestrictions(userId);
final ArrayList<String> pendingComponents = mPendingBroadcasts.get(userId, packageName);
if (pendingComponents == null) {
@@ -5214,10 +5144,7 @@
final int hasAccessInstantApps = mContext.checkCallingOrSelfPermission(
android.Manifest.permission.ACCESS_INSTANT_APPS);
- final AndroidPackage pkg;
- synchronized (mLock) {
- pkg = mPackages.get(packageName);
- }
+ final AndroidPackage pkg = getPackage(packageName);
// Queue up an async operation since the package deletion may take a little while.
mHandler.post(() -> {
@@ -5438,8 +5365,8 @@
}
}
resolver.addFilter(newFilter);
- scheduleWritePackageRestrictionsLocked(sourceUserId);
}
+ scheduleWritePackageRestrictions(sourceUserId);
}
@Override
@@ -5460,8 +5387,8 @@
resolver.removeFilter(filter);
}
}
- scheduleWritePackageRestrictionsLocked(sourceUserId);
}
+ scheduleWritePackageRestrictions(sourceUserId);
}
// Enforcing that callingUid is owning pkg on userId
@@ -5733,12 +5660,8 @@
@Override
public void setUpdateAvailable(String packageName, boolean updateAvailable) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
- synchronized (mLock) {
- final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
- if (pkgSetting != null) {
- pkgSetting.setUpdateAvailable(updateAvailable);
- }
- }
+ commitPackageStateMutation(null, packageName, state ->
+ state.setUpdateAvailable(updateAvailable));
}
@Override
@@ -5763,32 +5686,32 @@
}
int callingUid = Binder.getCallingUid();
-
String componentPkgName = componentName.getPackageName();
- int componentUid = getPackageUid(componentPkgName, 0, userId);
- if (!UserHandle.isSameApp(callingUid, componentUid)) {
- throw new SecurityException("The calling UID (" + callingUid + ")"
- + " does not match the target UID");
- }
- String allowedCallerPkg = mContext.getString(R.string.config_overrideComponentUiPackage);
- if (TextUtils.isEmpty(allowedCallerPkg)) {
- throw new SecurityException(
- "There is no package defined as allowed to change a component's label or icon");
- }
+ boolean changed = executeWithConsistentComputerReturning(computer -> {
+ int componentUid = getPackageUid(componentPkgName, 0, userId);
+ if (!UserHandle.isSameApp(callingUid, componentUid)) {
+ throw new SecurityException("The calling UID (" + callingUid + ")"
+ + " does not match the target UID");
+ }
- int allowedCallerUid = getPackageUid(allowedCallerPkg, PackageManager.MATCH_SYSTEM_ONLY,
- userId);
- if (allowedCallerUid == -1 || !UserHandle.isSameApp(callingUid, allowedCallerUid)) {
- throw new SecurityException("The calling UID (" + callingUid + ")"
- + " is not allowed to change a component's label or icon");
- }
+ String allowedCallerPkg =
+ mContext.getString(R.string.config_overrideComponentUiPackage);
+ if (TextUtils.isEmpty(allowedCallerPkg)) {
+ throw new SecurityException( "There is no package defined as allowed to change a "
+ + "component's label or icon");
+ }
- synchronized (mLock) {
- AndroidPackage pkg = mPackages.get(componentPkgName);
- PackageSetting pkgSetting = getPackageSettingForMutation(componentPkgName);
- if (pkg == null || pkgSetting == null
- || (!pkg.isSystem() && !pkgSetting.getPkgState().isUpdatedSystemApp())) {
+ int allowedCallerUid = getPackageUid(allowedCallerPkg, PackageManager.MATCH_SYSTEM_ONLY,
+ userId);
+ if (allowedCallerUid == -1 || !UserHandle.isSameApp(callingUid, allowedCallerUid)) {
+ throw new SecurityException("The calling UID (" + callingUid + ")"
+ + " is not allowed to change a component's label or icon");
+ }
+ PackageStateInternal packageState = computer.getPackageStateInternal(componentPkgName);
+ if (packageState == null || packageState.getPkg() == null
+ || (!packageState.isSystem()
+ && !packageState.getTransientState().isUpdatedSystemApp())) {
throw new SecurityException(
"Changing the label is not allowed for " + componentName);
}
@@ -5797,13 +5720,23 @@
throw new IllegalArgumentException("Component " + componentName + " not found");
}
- if (!pkgSetting.overrideNonLocalizedLabelAndIcon(componentName, nonLocalizedLabel,
- icon, userId)) {
- // Nothing changed
- return;
- }
+ Pair<String, Integer> overrideLabelIcon = packageState.getUserStateOrDefault(userId)
+ .getOverrideLabelIconForComponent(componentName);
+
+ String existingLabel = overrideLabelIcon == null ? null : overrideLabelIcon.first;
+ Integer existingIcon = overrideLabelIcon == null ? null : overrideLabelIcon.second;
+
+ return !TextUtils.equals(existingLabel, nonLocalizedLabel)
+ || !Objects.equals(existingIcon, icon);
+ });
+ if (!changed) {
+ return;
}
+ commitPackageStateMutation(null, componentPkgName,
+ state -> state.userState(userId)
+ .setComponentLabelIcon(componentName, nonLocalizedLabel, icon));
+
ArrayList<String> components = mPendingBroadcasts.get(userId, componentPkgName);
if (components == null) {
components = new ArrayList<>();
@@ -6076,7 +6009,7 @@
if (isSynchronous) {
flushPackageRestrictionsAsUserInternalLocked(userId);
} else {
- scheduleWritePackageRestrictionsLocked(userId);
+ scheduleWritePackageRestrictions(userId);
}
if (scheduleBroadcastMessage) {
if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
@@ -6187,9 +6120,11 @@
// NOTE: this invokes synchronous disk access, so callers using this
// method should consider running on a background thread
mSettings.writePackageRestrictionsLPr(userId);
- mDirtyUsers.remove(userId);
- if (mDirtyUsers.isEmpty()) {
- mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
+ synchronized (mDirtyUsers) {
+ mDirtyUsers.remove(userId);
+ if (mDirtyUsers.isEmpty()) {
+ mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
+ }
}
}
@@ -6216,29 +6151,50 @@
public void setPackageStoppedState(String packageName, boolean stopped, int userId) {
if (!mUserManager.exists(userId)) return;
final int callingUid = Binder.getCallingUid();
- if (getInstantAppPackageName(callingUid) != null) {
- return;
- }
- final int permission = mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
- final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
- if (!allowedByPermission
- && !ArrayUtils.contains(getPackagesForUid(callingUid), packageName)) {
- throw new SecurityException(
- "Permission Denial: attempt to change stopped state from pid="
- + Binder.getCallingPid()
- + ", uid=" + callingUid + ", package=" + packageName);
- }
- enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
- true /* checkShell */, "stop package");
- // writer
- synchronized (mLock) {
- final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (!shouldFilterApplication(ps, callingUid, userId)
- && mSettings.setPackageStoppedStateLPw(this, packageName, stopped, userId)) {
- scheduleWritePackageRestrictionsLocked(userId);
+ Pair<Boolean, String> wasNotLaunchedAndInstallerPackageName =
+ executeWithConsistentComputerReturningThrowing(computer -> {
+ if (computer.getInstantAppPackageName(callingUid) != null) {
+ return null;
}
+ final int permission = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+ final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
+ if (!allowedByPermission
+ && !ArrayUtils.contains(computer.getPackagesForUid(callingUid), packageName)) {
+ throw new SecurityException(
+ "Permission Denial: attempt to change stopped state from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + callingUid + ", package=" + packageName);
+ }
+ computer.enforceCrossUserPermission(callingUid, userId,
+ true /* requireFullPermission */, true /* checkShell */, "stop package");
+
+ final PackageStateInternal packageState = computer.getPackageStateInternal(packageName);
+ final PackageUserState PackageUserState = packageState == null
+ ? null : packageState.getUserStateOrDefault(userId);
+ if (packageState == null
+ || computer.shouldFilterApplication(packageState, callingUid, userId)
+ || PackageUserState.isStopped() == stopped) {
+ return null;
+ }
+
+ return Pair.create(PackageUserState.isNotLaunched(),
+ packageState.getInstallSource().installerPackageName);
+ });
+ if (wasNotLaunchedAndInstallerPackageName != null) {
+ boolean wasNotLaunched = wasNotLaunchedAndInstallerPackageName.first;
+
+ commitPackageStateMutation(null, packageName, packageState -> {
+ PackageUserStateWrite userState = packageState.userState(userId);
+ userState.setStopped(stopped);
+ if (wasNotLaunched) {
+ userState.setNotLaunched(false);
+ }
+ });
+
+ scheduleWritePackageRestrictions(userId);
}
+
// If this would cause the app to leave force-stop, then also make sure to unhibernate the
// app if needed.
if (!stopped) {
@@ -6490,19 +6446,17 @@
}
void dumpSnapshotStats(PrintWriter pw, boolean isBrief) {
- if (!mSnapshotEnabled) {
- pw.println(" Snapshots disabled");
- } else {
- int hits = 0;
- int level = sSnapshotCorked.get();
- synchronized (mSnapshotLock) {
- if (mSnapshotComputer != null) {
- hits = mSnapshotComputer.getUsed();
- }
- }
- final long now = SystemClock.currentTimeMicro();
- mSnapshotStatistics.dump(pw, " ", now, hits, level, isBrief);
+ if (mSnapshotStatistics == null) {
+ return;
}
+ int hits = 0;
+ synchronized (mSnapshotLock) {
+ if (mSnapshotComputer != null) {
+ hits = mSnapshotComputer.getUsed();
+ }
+ }
+ final long now = SystemClock.currentTimeMicro();
+ mSnapshotStatistics.dump(pw, " ", now, hits, -1, isBrief);
}
/**
@@ -6661,15 +6615,17 @@
/** Called by UserManagerService */
void cleanUpUser(UserManagerService userManager, @UserIdInt int userId) {
synchronized (mLock) {
- mDirtyUsers.remove(userId);
+ synchronized (mDirtyUsers) {
+ mDirtyUsers.remove(userId);
+ }
mUserNeedsBadging.delete(userId);
mPermissionManager.onUserRemoved(userId);
mSettings.removeUserLPw(userId);
mPendingBroadcasts.remove(userId);
- mInstantAppRegistry.onUserRemovedLPw(userId);
mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId);
mAppsFilter.onUserDeleted(userId);
}
+ mInstantAppRegistry.onUserRemoved(userId);
}
/**
@@ -6688,7 +6644,7 @@
userTypeInstallablePackages, disallowedPackages);
}
synchronized (mLock) {
- scheduleWritePackageRestrictionsLocked(userId);
+ scheduleWritePackageRestrictions(userId);
scheduleWritePackageListLocked(userId);
mAppsFilter.onUserCreated(userId);
}
@@ -6796,20 +6752,23 @@
return mComputer.isPackageSignedByKeySetExactly(packageName, ks);
}
- @GuardedBy("mLock")
- private void deletePackageIfUnusedLPr(final String packageName) {
- PackageSetting ps = mSettings.getPackageLPr(packageName);
+ private void deletePackageIfUnused(final String packageName) {
+ PackageStateInternal ps = getPackageStateInternal(packageName);
if (ps == null) {
return;
}
- if (!ps.isAnyInstalled(mUserManager.getUserIds())) {
- // TODO Implement atomic delete if package is unused
- // It is currently possible that the package will be deleted even if it is installed
- // after this method returns.
- mHandler.post(() -> mDeletePackageHelper.deletePackageX(
- packageName, PackageManager.VERSION_CODE_HIGHEST,
- 0, PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/));
+ final SparseArray<? extends PackageUserStateInternal> userStates = ps.getUserStates();
+ for (int index = 0; index < userStates.size(); index++) {
+ if (userStates.valueAt(index).isInstalled()) {
+ return;
+ }
}
+ // TODO Implement atomic delete if package is unused
+ // It is currently possible that the package will be deleted even if it is installed
+ // after this method returns.
+ mHandler.post(() -> mDeletePackageHelper.deletePackageX(
+ packageName, PackageManager.VERSION_CODE_HIGHEST,
+ 0, PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/));
}
private AndroidPackage getPackage(String packageName) {
@@ -6985,7 +6944,7 @@
@Override
public PackageList getPackageList(@Nullable PackageListObserver observer) {
final ArrayList<String> list = new ArrayList<>();
- forEachPackageState(false, packageState -> {
+ forEachPackageState(packageState -> {
AndroidPackage pkg = packageState.getPkg();
if (pkg != null) {
list.add(pkg.getPackageName());
@@ -6993,18 +6952,14 @@
});
final PackageList packageList = new PackageList(list, observer);
if (observer != null) {
- synchronized (mLock) {
- mPackageListObservers.add(packageList);
- }
+ mPackageObserverHelper.addObserver(packageList);
}
return packageList;
}
@Override
public void removePackageListObserver(PackageListObserver observer) {
- synchronized (mLock) {
- mPackageListObservers.remove(observer);
- }
+ mPackageObserverHelper.removeObserver(observer);
}
@Override
@@ -7203,6 +7158,16 @@
SparseArray<String> profileOwnerPackages) {
mProtectedPackages.setDeviceAndProfileOwnerPackages(
deviceOwnerUserId, deviceOwnerPackage, profileOwnerPackages);
+ final ArraySet<Integer> usersWithPoOrDo = new ArraySet<>();
+ if (deviceOwnerPackage != null) {
+ usersWithPoOrDo.add(deviceOwnerUserId);
+ }
+ final int sz = profileOwnerPackages.size();
+ for (int i = 0; i < sz; i++) {
+ if (profileOwnerPackages.valueAt(i) != null) {
+ removeAllNonSystemPackageSuspensions(profileOwnerPackages.keyAt(i));
+ }
+ }
}
@Override
@@ -7276,32 +7241,32 @@
@Override
public void grantImplicitAccess(int userId, Intent intent,
int recipientAppId, int visibleUid, boolean direct, boolean retainOnUpdate) {
- synchronized (mLock) {
- final AndroidPackage visiblePackage = getPackage(visibleUid);
+ boolean accessGranted = executeWithConsistentComputerReturning(computer -> {
+ final AndroidPackage visiblePackage = computer.getPackage(visibleUid);
final int recipientUid = UserHandle.getUid(userId, recipientAppId);
- if (visiblePackage == null || getPackage(recipientUid) == null) {
- return;
+ if (visiblePackage == null || computer.getPackage(recipientUid) == null) {
+ return false;
}
- final boolean instantApp =
- isInstantAppInternal(visiblePackage.getPackageName(), userId, visibleUid);
- final boolean accessGranted;
+ final boolean instantApp = computer.isInstantAppInternal(
+ visiblePackage.getPackageName(), userId, visibleUid);
if (instantApp) {
if (!direct) {
// if the interaction that lead to this granting access to an instant app
// was indirect (i.e.: URI permission grant), do not actually execute the
// grant.
- return;
+ return false;
}
- accessGranted = mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
+ return mInstantAppRegistry.grantInstantAccess(userId, intent,
recipientAppId, UserHandle.getAppId(visibleUid) /*instantAppId*/);
} else {
- accessGranted = mAppsFilter.grantImplicitAccess(recipientUid, visibleUid,
+ return mAppsFilter.grantImplicitAccess(recipientUid, visibleUid,
retainOnUpdate);
}
- if (accessGranted) {
- ApplicationPackageManager.invalidateGetPackagesForUidCache();
- }
+ });
+
+ if (accessGranted) {
+ ApplicationPackageManager.invalidateGetPackagesForUidCache();
}
}
@@ -7314,7 +7279,8 @@
@Override
public void pruneInstantApps() {
- mInstantAppRegistry.pruneInstantApps();
+ executeWithConsistentComputer(computer ->
+ mInstantAppRegistry.pruneInstantApps(computer));
}
@Override
@@ -7361,7 +7327,7 @@
@Override
public List<PackageInfo> getOverlayPackages(int userId) {
final ArrayList<PackageInfo> overlayPackages = new ArrayList<>();
- forEachPackageState(false, packageState -> {
+ forEachPackageState(packageState -> {
final AndroidPackage pkg = packageState.getPkg();
if (pkg != null && pkg.getOverlayTarget() != null) {
PackageInfo pkgInfo = generatePackageInfo(packageState, 0, userId);
@@ -7377,7 +7343,7 @@
@Override
public List<String> getTargetPackageNames(int userId) {
List<String> targetPackages = new ArrayList<>();
- forEachPackageState(false, packageState -> {
+ forEachPackageState(packageState -> {
final AndroidPackage pkg = packageState.getPkg();
if (pkg != null && !pkg.isOverlay()) {
targetPackages.add(pkg.getPackageName());
@@ -7390,53 +7356,8 @@
public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName,
@Nullable OverlayPaths overlayPaths,
@NonNull Set<String> outUpdatedPackageNames) {
- boolean modified = false;
- synchronized (mLock) {
- final AndroidPackage targetPkg = mPackages.get(targetPackageName);
- if (targetPackageName == null || targetPkg == null) {
- Slog.e(TAG, "failed to find package " + targetPackageName);
- return false;
- }
-
- if (targetPkg.getLibraryNames() != null) {
- // Set the overlay paths for dependencies of the shared library.
- for (final String libName : targetPkg.getLibraryNames()) {
- final SharedLibraryInfo info = getSharedLibraryInfo(libName,
- SharedLibraryInfo.VERSION_UNDEFINED);
- if (info == null) {
- continue;
- }
- final List<VersionedPackage> dependents = getPackagesUsingSharedLibrary(
- info, 0, Process.SYSTEM_UID, userId);
- if (dependents == null) {
- continue;
- }
- for (final VersionedPackage dependent : dependents) {
- final PackageSetting ps = mSettings.getPackageLPr(
- dependent.getPackageName());
- if (ps == null) {
- continue;
- }
- if (ps.setOverlayPathsForLibrary(libName, overlayPaths, userId)) {
- outUpdatedPackageNames.add(dependent.getPackageName());
- modified = true;
- }
- }
- }
- }
-
- final PackageSetting ps = mSettings.getPackageLPr(targetPackageName);
- if (ps.setOverlayPaths(overlayPaths, userId)) {
- outUpdatedPackageNames.add(targetPackageName);
- modified = true;
- }
-
- if (modified) {
- invalidatePackageInfoCache();
- }
- }
-
- return true;
+ return PackageManagerService.this.setEnabledOverlayPackages(userId, targetPackageName,
+ overlayPaths, outUpdatedPackageNames);
}
@Override
@@ -7504,15 +7425,13 @@
@Override
public boolean hasInstantApplicationMetadata(String packageName, int userId) {
- synchronized (mLock) {
- return mInstantAppRegistry.hasInstantApplicationMetadataLPr(packageName, userId);
- }
+ return mInstantAppRegistry.hasInstantApplicationMetadata(packageName, userId);
}
@Override
public void notifyPackageUse(String packageName, int reason) {
synchronized (mLock) {
- PackageManagerService.this.notifyPackageUseLocked(packageName, reason);
+ PackageManagerService.this.notifyPackageUseInternal(packageName, reason);
}
}
@@ -7549,30 +7468,24 @@
}
@Override
- public void forEachPackage(Consumer<AndroidPackage> actionLocked) {
- PackageManagerService.this.forEachPackage(actionLocked);
- }
-
- @Override
public void forEachPackageSetting(Consumer<PackageSetting> actionLocked) {
PackageManagerService.this.forEachPackageSetting(actionLocked);
}
@Override
- public void forEachPackageState(boolean locked, Consumer<PackageStateInternal> action) {
- PackageManagerService.this.forEachPackageState(locked, action);
+ public void forEachPackageState(Consumer<PackageStateInternal> action) {
+ PackageManagerService.this.forEachPackageState(action);
}
@Override
- public void forEachInstalledPackage(@NonNull Consumer<AndroidPackage> actionLocked,
+ public void forEachPackage(Consumer<AndroidPackage> action) {
+ PackageManagerService.this.forEachPackage(action);
+ }
+
+ @Override
+ public void forEachInstalledPackage(@NonNull Consumer<AndroidPackage> action,
@UserIdInt int userId) {
- forEachInstalledPackage(true, actionLocked, userId);
- }
-
- @Override
- public void forEachInstalledPackage(boolean locked,
- @NonNull Consumer<AndroidPackage> action, int userId) {
- PackageManagerService.this.forEachInstalledPackage(locked, action, userId);
+ PackageManagerService.this.forEachInstalledPackage(action, userId);
}
@Override
@@ -7695,9 +7608,7 @@
@Override
public void updateRuntimePermissionsFingerprint(@UserIdInt int userId) {
- synchronized (mLock) {
- mSettings.updateRuntimePermissionsFingerprintLPr(userId);
- }
+ mSettings.updateRuntimePermissionsFingerprint(userId);
}
@Override
@@ -7736,9 +7647,7 @@
@Override
public boolean isPermissionUpgradeNeeded(int userId) {
- synchronized (mLock) {
- return mSettings.isPermissionUpgradeNeededLPr(userId);
- }
+ return mSettings.isPermissionUpgradeNeeded(userId);
}
@Override
@@ -7910,14 +7819,104 @@
}
}
+ private boolean setEnabledOverlayPackages(@UserIdInt int userId,
+ @NonNull String targetPackageName, @Nullable OverlayPaths newOverlayPaths,
+ @NonNull Set<String> outUpdatedPackageNames) {
+ synchronized (mOverlayPathsLock) {
+ final ArrayMap<String, ArraySet<String>> libNameToModifiedDependents = new ArrayMap<>();
+ Boolean targetModified = executeWithConsistentComputerReturning(computer -> {
+ final PackageStateInternal packageState = computer.getPackageStateInternal(
+ targetPackageName);
+ final AndroidPackage targetPkg =
+ packageState == null ? null : packageState.getPkg();
+ if (targetPackageName == null || targetPkg == null) {
+ Slog.e(TAG, "failed to find package " + targetPackageName);
+ return null;
+ }
+
+ if (Objects.equals(packageState.getUserStateOrDefault(userId).getOverlayPaths(),
+ newOverlayPaths)) {
+ return false;
+ }
+
+ if (targetPkg.getLibraryNames() != null) {
+ // Set the overlay paths for dependencies of the shared library.
+ for (final String libName : targetPkg.getLibraryNames()) {
+ ArraySet<String> modifiedDependents = null;
+
+ final SharedLibraryInfo info = computer.getSharedLibraryInfo(libName,
+ SharedLibraryInfo.VERSION_UNDEFINED);
+ if (info == null) {
+ continue;
+ }
+ final List<VersionedPackage> dependents = computer
+ .getPackagesUsingSharedLibrary(info, 0, Process.SYSTEM_UID, userId);
+ if (dependents == null) {
+ continue;
+ }
+ for (final VersionedPackage dependent : dependents) {
+ final PackageStateInternal dependentState =
+ computer.getPackageStateInternal(dependent.getPackageName());
+ if (dependentState == null) {
+ continue;
+ }
+ if (!Objects.equals(dependentState.getUserStateOrDefault(userId)
+ .getSharedLibraryOverlayPaths()
+ .get(libName), newOverlayPaths)) {
+ String dependentPackageName = dependent.getPackageName();
+ modifiedDependents = ArrayUtils.add(modifiedDependents,
+ dependentPackageName);
+ outUpdatedPackageNames.add(dependentPackageName);
+ }
+ }
+
+ if (modifiedDependents != null) {
+ libNameToModifiedDependents.put(libName, modifiedDependents);
+ }
+ }
+ }
+
+ outUpdatedPackageNames.add(targetPackageName);
+ return true;
+ });
+
+ if (targetModified == null) {
+ // Null indicates error
+ return false;
+ } else if (!targetModified) {
+ // Treat non-modification as a successful commit
+ return true;
+ }
+
+ commitPackageStateMutation(null, mutator -> {
+ mutator.forPackage(targetPackageName)
+ .userState(userId)
+ .setOverlayPaths(newOverlayPaths);
+
+ for (int mapIndex = 0; mapIndex < libNameToModifiedDependents.size(); mapIndex++) {
+ String libName = libNameToModifiedDependents.keyAt(mapIndex);
+ ArraySet<String> modifiedDependents =
+ libNameToModifiedDependents.valueAt(mapIndex);
+ for (int setIndex = 0; setIndex < modifiedDependents.size(); setIndex++) {
+ mutator.forPackage(modifiedDependents.valueAt(setIndex))
+ .userState(userId)
+ .setOverlayPathsForLibrary(libName, newOverlayPaths);
+ }
+ }
+ });
+ }
+
+ invalidatePackageInfoCache();
+
+ return true;
+ }
+
@Override
public int getRuntimePermissionsVersion(@UserIdInt int userId) {
Preconditions.checkArgumentNonnegative(userId);
enforceAdjustRuntimePermissionsPolicyOrUpgradeRuntimePermissions(
"getRuntimePermissionVersion");
- synchronized (mLock) {
- return mSettings.getDefaultRuntimePermissionsVersionLPr(userId);
- }
+ return mSettings.getDefaultRuntimePermissionsVersion(userId);
}
@Override
@@ -7926,9 +7925,7 @@
Preconditions.checkArgumentNonnegative(userId);
enforceAdjustRuntimePermissionsPolicyOrUpgradeRuntimePermissions(
"setRuntimePermissionVersion");
- synchronized (mLock) {
- mSettings.setDefaultRuntimePermissionsVersionLPr(version, userId);
- }
+ mSettings.setDefaultRuntimePermissionsVersion(version, userId);
}
private void enforceAdjustRuntimePermissionsPolicyOrUpgradeRuntimePermissions(
@@ -7964,58 +7961,19 @@
@VisibleForTesting(visibility = Visibility.PRIVATE)
@Nullable
PackageStateInternal getPackageStateInternal(String packageName) {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- PackageSetting pkgSetting =
- (PackageSetting) computer.getPackageStateInternal(packageName);
- if (pkgSetting == null) {
- return null;
- }
-
- return new PackageSetting(pkgSetting);
- }
- } else {
- return computer.getPackageStateInternal(packageName);
- }
+ return mComputer.getPackageStateInternal(packageName);
}
@Nullable
PackageStateInternal getPackageStateInternal(String packageName, int callingUid) {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- PackageSetting pkgSetting =
- (PackageSetting) computer.getPackageStateInternal(packageName, callingUid);
- if (pkgSetting == null) {
- return null;
- }
-
- return new PackageSetting(pkgSetting);
- }
- } else {
- return computer.getPackageStateInternal(packageName, callingUid);
- }
+ return mComputer.getPackageStateInternal(packageName, callingUid);
}
@Nullable
PackageStateInternal getPackageStateInstalledFiltered(@NonNull String packageName,
int callingUid, @UserIdInt int userId) {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- PackageSetting pkgSetting =
- (PackageSetting) filterPackageStateForInstalledAndFiltered(computer,
- packageName, callingUid, userId);
- if (pkgSetting == null) {
- return null;
- }
- return new PackageSetting(pkgSetting);
- }
- } else {
- return filterPackageStateForInstalledAndFiltered(computer, packageName, callingUid,
- userId);
- }
+ return filterPackageStateForInstalledAndFiltered(mComputer, packageName, callingUid,
+ userId);
}
@Nullable
@@ -8057,31 +8015,19 @@
}
}
- void forEachPackageState(boolean locked, Consumer<PackageStateInternal> action) {
- if (locked) {
- synchronized (mLiveComputerSyncLock) {
- forEachPackageState(mComputer.getPackageStates(), action);
- }
- } else {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- forEachPackageState(computer.getPackageStates(), action);
- }
- } else {
- forEachPackageState(computer.getPackageStates(), action);
- }
- }
+ void forEachPackageState(Consumer<PackageStateInternal> consumer) {
+ forEachPackageState(mComputer.getPackageStates(), consumer);
}
- void forEachPackage(Consumer<AndroidPackage> action) {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- forEachPackage(computer.getPackageStates(), action);
+ void forEachPackage(Consumer<AndroidPackage> consumer) {
+ final ArrayMap<String, ? extends PackageStateInternal> packageStates =
+ mComputer.getPackageStates();
+ int size = packageStates.size();
+ for (int index = 0; index < size; index++) {
+ PackageStateInternal packageState = packageStates.valueAt(index);
+ if (packageState.getPkg() != null) {
+ consumer.accept(packageState.getPkg());
}
- } else {
- forEachPackage(computer.getPackageStates(), action);
}
}
@@ -8095,19 +8041,7 @@
}
}
- private void forEachPackage(
- @NonNull ArrayMap<String, ? extends PackageStateInternal> packageStates,
- @NonNull Consumer<AndroidPackage> consumer) {
- int size = packageStates.size();
- for (int index = 0; index < size; index++) {
- PackageStateInternal packageState = packageStates.valueAt(index);
- if (packageState.getPkg() != null) {
- consumer.accept(packageState.getPkg());
- }
- }
- }
-
- void forEachInstalledPackage(boolean locked, @NonNull Consumer<AndroidPackage> action,
+ void forEachInstalledPackage(@NonNull Consumer<AndroidPackage> action,
@UserIdInt int userId) {
Consumer<PackageStateInternal> actionWrapped = packageState -> {
if (packageState.getPkg() != null
@@ -8115,84 +8049,37 @@
action.accept(packageState.getPkg());
}
};
- if (locked) {
- synchronized (mLiveComputerSyncLock) {
- forEachPackageState(mComputer.getPackageStates(), actionWrapped);
- }
- } else {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- forEachPackageState(computer.getPackageStates(), actionWrapped);
- }
- } else {
- forEachPackageState(computer.getPackageStates(), actionWrapped);
- }
- }
+ forEachPackageState(mComputer.getPackageStates(), actionWrapped);
}
- private void executeWithConsistentComputer(
+ // TODO: Make private
+ void executeWithConsistentComputer(
@NonNull FunctionalUtils.ThrowingConsumer<Computer> consumer) {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- consumer.accept(computer);
- }
- } else {
- consumer.accept(computer);
- }
+ consumer.accept(snapshotComputer());
}
private <T> T executeWithConsistentComputerReturning(
@NonNull FunctionalUtils.ThrowingFunction<Computer, T> function) {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- return function.apply(computer);
- }
- } else {
- return function.apply(computer);
- }
+ return function.apply(snapshotComputer());
}
private <ExceptionType extends Exception> void executeWithConsistentComputerThrowing(
@NonNull FunctionalUtils.ThrowingCheckedConsumer<Computer, ExceptionType> consumer)
throws ExceptionType {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- consumer.accept(computer);
- }
- } else {
- consumer.accept(computer);
- }
+ consumer.accept(snapshotComputer());
}
private <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
executeWithConsistentComputerThrowing2(
@NonNull FunctionalUtils.ThrowingChecked2Consumer<Computer, ExceptionOne,
ExceptionTwo> consumer) throws ExceptionOne, ExceptionTwo {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- consumer.accept(computer);
- }
- } else {
- consumer.accept(computer);
- }
+ consumer.accept(snapshotComputer());
}
private <T, ExceptionType extends Exception> T executeWithConsistentComputerReturningThrowing(
@NonNull FunctionalUtils.ThrowingCheckedFunction<Computer, T, ExceptionType> function)
throws ExceptionType {
- Computer computer = snapshotComputer();
- if (computer == mLiveComputer) {
- synchronized (mLiveComputerSyncLock) {
- return function.apply(computer);
- }
- } else {
- return function.apply(computer);
- }
+ return function.apply(snapshotComputer());
}
boolean isHistoricalPackageUsageAvailable() {
@@ -8276,9 +8163,7 @@
if (!isInstantApp(packageName, userId)) {
return null;
}
- synchronized (mLock) {
- return mInstantAppRegistry.getInstantAppAndroidIdLPw(packageName, userId);
- }
+ return mInstantAppRegistry.getInstantAppAndroidId(packageName, userId);
}
@Override
@@ -8333,10 +8218,13 @@
+ SET_HARMFUL_APP_WARNINGS + " permission.");
}
- synchronized (mLock) {
- mSettings.setHarmfulAppWarningLPw(packageName, warning, userId);
- scheduleWritePackageRestrictionsLocked(userId);
+ PackageStateMutator.Result result = commitPackageStateMutation(null, packageName,
+ packageState -> packageState.userState(userId)
+ .setHarmfulAppWarning(warning == null ? null : warning.toString()));
+ if (result.isSpecificPackageNull()) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
}
+ scheduleWritePackageRestrictions(userId);
}
@Nullable
@@ -8388,14 +8276,23 @@
@Override
public void setMimeGroup(String packageName, String mimeGroup, List<String> mimeTypes) {
enforceOwnerRights(packageName, Binder.getCallingUid());
- final boolean changed;
- synchronized (mLock) {
- changed = mSettings.getPackageLPr(packageName).setMimeGroup(mimeGroup,
- new ArraySet<>(mimeTypes));
+ mimeTypes = CollectionUtils.emptyIfNull(mimeTypes);
+ final PackageStateInternal packageState = getPackageStateInternal(packageName);
+ Set<String> existingMimeTypes = packageState.getMimeGroups().get(mimeGroup);
+ if (existingMimeTypes == null) {
+ throw new IllegalArgumentException("Unknown MIME group " + mimeGroup
+ + " for package " + packageName);
}
- if (changed) {
- applyMimeGroupChanges(packageName, mimeGroup);
+ if (existingMimeTypes.size() == mimeTypes.size()
+ && existingMimeTypes.containsAll(mimeTypes)) {
+ return;
}
+
+ ArraySet<String> mimeTypesSet = new ArraySet<>(mimeTypes);
+ commitPackageStateMutation(null, packageName, packageStateWrite -> {
+ packageStateWrite.setMimeGroup(mimeGroup, mimeTypesSet);
+ });
+ applyMimeGroupChanges(packageName, mimeGroup);
}
@Override
@@ -8426,8 +8323,15 @@
enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
false /* checkShell */, "setSplashScreenTheme");
enforceOwnerRights(packageName, callingUid);
- mutateInstalledPackageSetting(packageName, callingUid, userId,
- pkgSetting -> pkgSetting.setSplashScreenTheme(userId, themeId));
+
+ PackageStateInternal packageState = getPackageStateInstalledFiltered(packageName,
+ callingUid, userId);
+ if (packageState == null) {
+ return;
+ }
+
+ commitPackageStateMutation(null, packageName, state ->
+ state.userState(userId).setSplashScreenTheme(themeId));
}
@Override
@@ -8442,6 +8346,7 @@
* Temporary method that wraps mSettings.writeLPr() and calls mPermissionManager's
* writeLegacyPermissionsTEMP() beforehand.
*
+ * TODO: In the meantime, can this be moved to a schedule call?
* TODO(b/182523293): This should be removed once we finish migration of permission storage.
*/
void writeSettingsLPrTEMP() {
@@ -8532,57 +8437,54 @@
}
final int[] allUsers = mInjector.getUserManagerService().getUserIds();
+ final List<PerUidReadTimeouts> result = new ArrayList<>(perPackageReadTimeouts.size());
+ for (int i = 0, size = perPackageReadTimeouts.size(); i < size; ++i) {
+ final PerPackageReadTimeouts perPackage = perPackageReadTimeouts.get(i);
+ final PackageStateInternal ps = getPackageStateInternal(perPackage.packageName);
+ if (ps == null) {
+ if (DEBUG_PER_UID_READ_TIMEOUTS) {
+ Slog.i(TAG, "PerUidReadTimeouts: package not found = "
+ + perPackage.packageName);
+ }
+ continue;
+ }
+ if (ps.getAppId() < Process.FIRST_APPLICATION_UID) {
+ if (DEBUG_PER_UID_READ_TIMEOUTS) {
+ Slog.i(TAG, "PerUidReadTimeouts: package is system, appId="
+ + ps.getAppId());
+ }
+ continue;
+ }
- List<PerUidReadTimeouts> result = new ArrayList<>(perPackageReadTimeouts.size());
- synchronized (mLock) {
- for (int i = 0, size = perPackageReadTimeouts.size(); i < size; ++i) {
- final PerPackageReadTimeouts perPackage = perPackageReadTimeouts.get(i);
- final PackageSetting ps = mSettings.mPackages.get(perPackage.packageName);
- if (ps == null) {
- if (DEBUG_PER_UID_READ_TIMEOUTS) {
- Slog.i(TAG, "PerUidReadTimeouts: package not found = "
- + perPackage.packageName);
- }
+ final AndroidPackage pkg = ps.getPkg();
+ if (pkg.getLongVersionCode() < perPackage.versionCodes.minVersionCode
+ || pkg.getLongVersionCode() > perPackage.versionCodes.maxVersionCode) {
+ if (DEBUG_PER_UID_READ_TIMEOUTS) {
+ Slog.i(TAG, "PerUidReadTimeouts: version code is not in range = "
+ + perPackage.packageName + ":" + pkg.getLongVersionCode());
+ }
+ continue;
+ }
+ if (perPackage.sha256certificate != null
+ && !pkg.getSigningDetails().hasSha256Certificate(
+ perPackage.sha256certificate)) {
+ if (DEBUG_PER_UID_READ_TIMEOUTS) {
+ Slog.i(TAG, "PerUidReadTimeouts: invalid certificate = "
+ + perPackage.packageName + ":" + pkg.getLongVersionCode());
+ }
+ continue;
+ }
+ for (int userId : allUsers) {
+ if (!ps.getUserStateOrDefault(userId).isInstalled()) {
continue;
}
- if (ps.getAppId() < Process.FIRST_APPLICATION_UID) {
- if (DEBUG_PER_UID_READ_TIMEOUTS) {
- Slog.i(TAG, "PerUidReadTimeouts: package is system, appId="
- + ps.getAppId());
- }
- continue;
- }
-
- final AndroidPackage pkg = ps.getPkg();
- if (pkg.getLongVersionCode() < perPackage.versionCodes.minVersionCode
- || pkg.getLongVersionCode() > perPackage.versionCodes.maxVersionCode) {
- if (DEBUG_PER_UID_READ_TIMEOUTS) {
- Slog.i(TAG, "PerUidReadTimeouts: version code is not in range = "
- + perPackage.packageName + ":" + pkg.getLongVersionCode());
- }
- continue;
- }
- if (perPackage.sha256certificate != null
- && !pkg.getSigningDetails().hasSha256Certificate(
- perPackage.sha256certificate)) {
- if (DEBUG_PER_UID_READ_TIMEOUTS) {
- Slog.i(TAG, "PerUidReadTimeouts: invalid certificate = "
- + perPackage.packageName + ":" + pkg.getLongVersionCode());
- }
- continue;
- }
- for (int userId : allUsers) {
- if (!ps.getInstalled(userId)) {
- continue;
- }
- final int uid = UserHandle.getUid(userId, ps.getAppId());
- final PerUidReadTimeouts perUid = new PerUidReadTimeouts();
- perUid.uid = uid;
- perUid.minTimeUs = perPackage.timeouts.minTimeUs;
- perUid.minPendingTimeUs = perPackage.timeouts.minPendingTimeUs;
- perUid.maxPendingTimeUs = perPackage.timeouts.maxPendingTimeUs;
- result.add(perUid);
- }
+ final int uid = UserHandle.getUid(userId, ps.getAppId());
+ final PerUidReadTimeouts perUid = new PerUidReadTimeouts();
+ perUid.uid = uid;
+ perUid.minTimeUs = perPackage.timeouts.minTimeUs;
+ perUid.minPendingTimeUs = perPackage.timeouts.minPendingTimeUs;
+ perUid.maxPendingTimeUs = perPackage.timeouts.maxPendingTimeUs;
+ result.add(perUid);
}
}
return result.toArray(new PerUidReadTimeouts[result.size()]);
@@ -8600,33 +8502,23 @@
private void setKeepUninstalledPackagesInternal(List<String> packageList) {
Preconditions.checkNotNull(packageList);
- List<String> removedFromList = null;
- synchronized (mLock) {
- if (mKeepUninstalledPackages != null) {
- final int packagesCount = mKeepUninstalledPackages.size();
- for (int i = 0; i < packagesCount; i++) {
- String oldPackage = mKeepUninstalledPackages.get(i);
- if (packageList != null && packageList.contains(oldPackage)) {
- continue;
- }
- if (removedFromList == null) {
- removedFromList = new ArrayList<>();
- }
- removedFromList.add(oldPackage);
- }
- }
- mKeepUninstalledPackages = new ArrayList<>(packageList);
- if (removedFromList != null) {
- final int removedCount = removedFromList.size();
- for (int i = 0; i < removedCount; i++) {
- deletePackageIfUnusedLPr(removedFromList.get(i));
- }
+ synchronized (mKeepUninstalledPackages) {
+ List<String> toRemove = new ArrayList<>(mKeepUninstalledPackages);
+ toRemove.removeAll(packageList); // Do not remove anything still in the list
+
+ mKeepUninstalledPackages.clear();
+ mKeepUninstalledPackages.addAll(packageList);
+
+ for (int i = 0; i < toRemove.size(); i++) {
+ deletePackageIfUnused(toRemove.get(i));
}
}
}
boolean shouldKeepUninstalledPackageLPr(String packageName) {
- return mKeepUninstalledPackages != null && mKeepUninstalledPackages.contains(packageName);
+ synchronized (mKeepUninstalledPackages) {
+ return mKeepUninstalledPackages.contains(packageName);
+ }
}
@Override
@@ -8904,7 +8796,7 @@
*/
@NonNull
public PackageStateMutator.InitialState recordInitialState() {
- return mPackageStateMutator.initialState(mChangedPackagesSequenceNumber);
+ return mPackageStateMutator.initialState(mChangedPackagesTracker.getSequenceNumber());
}
/**
@@ -8917,7 +8809,7 @@
@NonNull Consumer<PackageStateMutator> consumer) {
synchronized (mPackageStateWriteLock) {
final PackageStateMutator.Result result = mPackageStateMutator.generateResult(
- initialState, mChangedPackagesSequenceNumber);
+ initialState, mChangedPackagesTracker.getSequenceNumber());
if (result != PackageStateMutator.Result.SUCCESS) {
return result;
}
@@ -8939,7 +8831,7 @@
@NonNull Consumer<PackageStateWrite> consumer) {
synchronized (mPackageStateWriteLock) {
final PackageStateMutator.Result result = mPackageStateMutator.generateResult(
- initialState, mChangedPackagesSequenceNumber);
+ initialState, mChangedPackagesTracker.getSequenceNumber());
if (result != PackageStateMutator.Result.SUCCESS) {
return result;
}
@@ -8951,9 +8843,14 @@
consumer.accept(state);
}
- onChanged();
+ state.onChanged();
}
return PackageStateMutator.Result.SUCCESS;
}
+
+ void notifyInstantAppPackageInstalled(String packageName, int[] newUsers) {
+ executeWithConsistentComputer(computer ->
+ mInstantAppRegistry.onPackageInstalled(computer, packageName, newUsers));
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index db60686..1caa76d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -57,6 +57,7 @@
public IncrementalManager incrementalManager;
public PackageInstallerService installerService;
public InstantAppRegistry instantAppRegistry;
+ public ChangedPackagesTracker changedPackagesTracker = new ChangedPackagesTracker();
public InstantAppResolverConnection instantAppResolverConnection;
public ComponentName instantAppResolverSettingsComponent;
public boolean isPreNmr1Upgrade;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index dcc4386..19c31e0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -463,18 +463,18 @@
* <p>The rationale is that {@code disabledPkg} is a PackageSetting backed by xml files in /data
* and is not tamperproof.
*/
- private static boolean matchSignatureInSystem(PackageSetting pkgSetting,
- PackageSetting disabledPkgSetting) {
- if (pkgSetting.getSigningDetails().checkCapability(
+ private static boolean matchSignatureInSystem(@NonNull String packageName,
+ @NonNull SigningDetails signingDetails, PackageSetting disabledPkgSetting) {
+ if (signingDetails.checkCapability(
disabledPkgSetting.getSigningDetails(),
SigningDetails.CertCapabilities.INSTALLED_DATA)
|| disabledPkgSetting.getSigningDetails().checkCapability(
- pkgSetting.getSigningDetails(),
+ signingDetails,
SigningDetails.CertCapabilities.ROLLBACK)) {
return true;
} else {
logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " +
- pkgSetting.getPackageName());
+ packageName);
return false;
}
}
@@ -536,7 +536,8 @@
}
if (!match && isApkVerificationForced(disabledPkgSetting)) {
- match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
+ match = matchSignatureInSystem(packageName, pkgSetting.getSigningDetails(),
+ disabledPkgSetting);
}
if (!match && isRollback) {
diff --git a/services/core/java/com/android/server/pm/PackageObserverHelper.java b/services/core/java/com/android/server/pm/PackageObserverHelper.java
new file mode 100644
index 0000000..ec42f2e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageObserverHelper.java
@@ -0,0 +1,86 @@
+/*
+ * 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.pm;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageManagerInternal.PackageListObserver;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+
+class PackageObserverHelper {
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ // True set of observers, immutable, used to iterate without blocking the lock, since
+ // callbacks can take a long time to return. The previous alternative used a bunch of
+ // list copies on each notify call, which is suboptimal in cases of few mutations and
+ // lots of notifications.
+ @NonNull
+ @GuardedBy("mLock")
+ private ArraySet<PackageListObserver> mActiveSnapshot = new ArraySet<>();
+
+ public void addObserver(@NonNull PackageListObserver observer) {
+ synchronized (mLock) {
+ ArraySet<PackageListObserver> set = new ArraySet<>(mActiveSnapshot);
+ set.add(observer);
+ mActiveSnapshot = set;
+ }
+ }
+
+ public void removeObserver(@NonNull PackageListObserver observer) {
+ synchronized (mLock) {
+ ArraySet<PackageListObserver> set = new ArraySet<>(mActiveSnapshot);
+ set.remove(observer);
+ mActiveSnapshot = set;
+ }
+ }
+
+ public void notifyAdded(@NonNull String packageName, int uid) {
+ ArraySet<PackageListObserver> observers;
+ synchronized (mLock) {
+ observers = mActiveSnapshot;
+ }
+ final int size = observers.size();
+ for (int index = 0; index < size; index++) {
+ observers.valueAt(index).onPackageAdded(packageName, uid);
+ }
+ }
+
+ public void notifyChanged(@NonNull String packageName, int uid) {
+ ArraySet<PackageListObserver> observers;
+ synchronized (mLock) {
+ observers = mActiveSnapshot;
+ }
+ final int size = observers.size();
+ for (int index = 0; index < size; index++) {
+ observers.valueAt(index).onPackageChanged(packageName, uid);
+ }
+ }
+
+ public void notifyRemoved(@NonNull String packageName, int uid) {
+ ArraySet<PackageListObserver> observers;
+ synchronized (mLock) {
+ observers = mActiveSnapshot;
+ }
+ final int size = observers.size();
+ for (int index = 0; index < size; index++) {
+ observers.valueAt(index).onPackageRemoved(packageName, uid);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 5fc840c..d2abc69 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -68,10 +68,10 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
-import java.util.function.Predicate;
/**
* Settings data for a particular package we know about.
+ *
* @hide
*/
@DataClass(genGetters = true, genConstructor = false, genSetters = false, genBuilder = false)
@@ -189,7 +189,7 @@
private boolean forceQueryableOverride;
@NonNull
- private PackageStateUnserialized pkgState = new PackageStateUnserialized();
+ private final PackageStateUnserialized pkgState = new PackageStateUnserialized(this);
@NonNull
private UUID mDomainSetId;
@@ -722,10 +722,6 @@
return readUserState(userId).getEnabledState();
}
- String getLastDisabledAppCaller(int userId) {
- return readUserState(userId).getLastDisableAppCaller();
- }
-
void setInstalled(boolean inst, int userId) {
modifyUserState(userId).setInstalled(inst);
onChanged();
@@ -753,14 +749,6 @@
onChanged();
}
- boolean setOverlayPaths(OverlayPaths overlayPaths, int userId) {
- boolean changed = modifyUserState(userId).setOverlayPaths(overlayPaths);
- if (changed) {
- onChanged();
- }
- return changed;
- }
-
@NonNull
OverlayPaths getOverlayPaths(int userId) {
return readUserState(userId).getOverlayPaths();
@@ -773,11 +761,6 @@
return changed;
}
- @NonNull
- Map<String, OverlayPaths> getOverlayPathsForLibrary(int userId) {
- return readUserState(userId).getSharedLibraryOverlayPaths();
- }
-
boolean isAnyInstalled(int[] users) {
for (int user: users) {
if (readUserState(user).isInstalled()) {
@@ -850,55 +833,6 @@
onChanged();
}
- boolean getSuspended(int userId) {
- return readUserState(userId).isSuspended();
- }
-
- boolean addOrUpdateSuspension(String suspendingPackage, SuspendDialogInfo dialogInfo,
- PersistableBundle appExtras, PersistableBundle launcherExtras, int userId) {
- final PackageUserStateImpl existingUserState = modifyUserState(userId);
- final SuspendParams newSuspendParams = SuspendParams.getInstanceOrNull(dialogInfo,
- appExtras, launcherExtras);
- if (existingUserState.getSuspendParams() == null) {
- existingUserState.setSuspendParams(new ArrayMap<>());
- }
- final SuspendParams oldSuspendParams =
- existingUserState.getSuspendParams().put(suspendingPackage, newSuspendParams);
- onChanged();
- return !Objects.equals(oldSuspendParams, newSuspendParams);
- }
-
- boolean removeSuspension(String suspendingPackage, int userId) {
- boolean wasModified = false;
- final PackageUserStateImpl existingUserState = modifyUserState(userId);
- if (existingUserState.getSuspendParams() != null) {
- if (existingUserState.getSuspendParams().remove(suspendingPackage) != null) {
- wasModified = true;
- }
- if (existingUserState.getSuspendParams().size() == 0) {
- existingUserState.setSuspendParams(null);
- }
- }
- onChanged();
- return wasModified;
- }
-
- void removeSuspension(Predicate<String> suspendingPackagePredicate, int userId) {
- final PackageUserStateImpl existingUserState = modifyUserState(userId);
- if (existingUserState.getSuspendParams() != null) {
- for (int i = existingUserState.getSuspendParams().size() - 1; i >= 0; i--) {
- final String suspendingPackage = existingUserState.getSuspendParams().keyAt(i);
- if (suspendingPackagePredicate.test(suspendingPackage)) {
- existingUserState.getSuspendParams().removeAt(i);
- }
- }
- if (existingUserState.getSuspendParams().size() == 0) {
- existingUserState.setSuspendParams(null);
- }
- }
- onChanged();
- }
-
public boolean getInstantApp(int userId) {
return readUserState(userId).isInstantApp();
}
@@ -1095,8 +1029,8 @@
}
/**
- * TODO (b/170263003) refactor to dump to permissiongr proto
- * Dumps the permissions that are granted to users for this package.
+ * TODO (b/170263003) refactor to dump to permissiongr proto Dumps the permissions that are
+ * granted to users for this package.
*/
void writePackageUserPermissionsProto(ProtoOutputStream proto, long fieldId,
List<UserInfo> users, LegacyPermissionDataProvider dataProvider) {
@@ -1154,16 +1088,6 @@
}
}
- void setHarmfulAppWarning(int userId, String harmfulAppWarning) {
- modifyUserState(userId).setHarmfulAppWarning(harmfulAppWarning);
- onChanged();
- }
-
- String getHarmfulAppWarning(int userId) {
- PackageUserState userState = readUserState(userId);
- return userState.getHarmfulAppWarning();
- }
-
/**
* @see #mPath
*/
@@ -1175,9 +1099,8 @@
}
/**
- * @see PackageUserStateImpl#overrideLabelAndIcon(ComponentName, String, Integer)
- *
* @param userId the specific user to change the label/icon for
+ * @see PackageUserStateImpl#overrideLabelAndIcon(ComponentName, String, Integer)
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public boolean overrideNonLocalizedLabelAndIcon(@NonNull ComponentName component,
@@ -1188,9 +1111,8 @@
}
/**
- * @see PackageUserStateImpl#resetOverrideComponentLabelIcon()
- *
* @param userId the specific user to reset
+ * @see PackageUserStateImpl#resetOverrideComponentLabelIcon()
*/
public void resetOverrideComponentLabelIcon(@UserIdInt int userId) {
modifyUserState(userId).resetOverrideComponentLabelIcon();
@@ -1198,19 +1120,9 @@
}
/**
- * @param userId the specified user to modify the theme for
- * @param themeName the theme name to persist
- * @see android.window.SplashScreen#setSplashScreenTheme(int)
- */
- public void setSplashScreenTheme(@UserIdInt int userId, @Nullable String themeName) {
- modifyUserState(userId).setSplashScreenTheme(themeName);
- onChanged();
- }
-
- /**
* @param userId the specified user to get the theme setting from
- * @return the theme name previously persisted for the user or null
- * if no splashscreen theme is persisted.
+ * @return the theme name previously persisted for the user or null if no splashscreen theme is
+ * persisted.
* @see android.window.SplashScreen#setSplashScreenTheme(int)
*/
@Nullable
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index 802f701..bb82e6a 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -102,9 +102,7 @@
if (DEBUG_PREFERRED) {
Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
}
- synchronized (mPm.mLock) {
- mPm.scheduleWritePackageRestrictionsLocked(userId);
- }
+ mPm.scheduleWritePackageRestrictions(userId);
}
if ((DEBUG_PREFERRED || debug) && body.mPreferredResolveInfo == null) {
Slog.v(TAG, "No preferred activity to return");
@@ -121,9 +119,7 @@
if (changedUsers.size() > 0) {
updateDefaultHomeNotLocked(changedUsers);
mPm.postPreferredActivityChangedBroadcast(userId);
- synchronized (mPm.mLock) {
- mPm.scheduleWritePackageRestrictionsLocked(userId);
- }
+ mPm.scheduleWritePackageRestrictions(userId);
}
}
@@ -214,7 +210,7 @@
Settings.removeFilters(pir, filter, existing);
}
pir.addFilter(new PreferredActivity(filter, match, set, activity, always));
- mPm.scheduleWritePackageRestrictionsLocked(userId);
+ mPm.scheduleWritePackageRestrictions(userId);
}
if (!(isHomeFilter(filter) && updateDefaultHomeNotLocked(userId))) {
mPm.postPreferredActivityChangedBroadcast(userId);
@@ -399,7 +395,7 @@
synchronized (mPm.mLock) {
mPm.mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter(
new PersistentPreferredActivity(filter, activity, true));
- mPm.scheduleWritePackageRestrictionsLocked(userId);
+ mPm.scheduleWritePackageRestrictions(userId);
}
if (isHomeFilter(filter)) {
updateDefaultHomeNotLocked(userId);
@@ -420,9 +416,7 @@
if (changed) {
updateDefaultHomeNotLocked(userId);
mPm.postPreferredActivityChangedBroadcast(userId);
- synchronized (mPm.mLock) {
- mPm.scheduleWritePackageRestrictionsLocked(userId);
- }
+ mPm.scheduleWritePackageRestrictions(userId);
}
}
@@ -603,9 +597,7 @@
}
updateDefaultHomeNotLocked(userId);
resetNetworkPolicies(userId);
- synchronized (mPm.mLock) {
- mPm.scheduleWritePackageRestrictionsLocked(userId);
- }
+ mPm.scheduleWritePackageRestrictions(userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 19f180f..c0e191f 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -29,6 +29,7 @@
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
+import com.android.server.pm.pkg.SharedUserApi;
import libcore.io.IoUtils;
@@ -346,7 +347,7 @@
}
private static int getTargetSdkVersionForSeInfo(AndroidPackage pkg,
- SharedUserSetting sharedUserSetting, PlatformCompat compatibility) {
+ SharedUserApi sharedUser, PlatformCompat compatibility) {
// Apps which share a sharedUserId must be placed in the same selinux domain. If this
// package is the first app installed as this shared user, set seInfoTargetSdkVersion to its
// targetSdkVersion. These are later adjusted in PackageManagerService's constructor to be
@@ -355,8 +356,8 @@
// NOTE: As new packages are installed / updated, the shared user's seinfoTargetSdkVersion
// will NOT be modified until next boot, even if a lower targetSdkVersion is used. This
// ensures that all packages continue to run in the same selinux domain.
- if ((sharedUserSetting != null) && (sharedUserSetting.packages.size() != 0)) {
- return sharedUserSetting.seInfoTargetSdkVersion;
+ if ((sharedUser != null) && (sharedUser.getPackages().size() != 0)) {
+ return sharedUser.getSeInfoTargetSdkVersion();
}
final ApplicationInfo appInfo = AndroidPackageUtils.generateAppInfoWithoutState(pkg);
if (compatibility.isChangeEnabledInternal(SELINUX_LATEST_CHANGES, appInfo)) {
@@ -376,18 +377,18 @@
* the ApplicationInfo instance of the package.
*
* @param pkg object representing the package to be labeled.
- * @param sharedUserSetting if the app shares a sharedUserId, then this has the shared setting.
+ * @param sharedUser if the app shares a sharedUserId, then this has the shared setting.
* @param compatibility the PlatformCompat service to ask about state of compat changes.
* @return String representing the resulting seinfo.
*/
- public static String getSeInfo(AndroidPackage pkg, SharedUserSetting sharedUserSetting,
+ public static String getSeInfo(AndroidPackage pkg, SharedUserApi sharedUser,
PlatformCompat compatibility) {
- final int targetSdkVersion = getTargetSdkVersionForSeInfo(pkg, sharedUserSetting,
+ final int targetSdkVersion = getTargetSdkVersionForSeInfo(pkg, sharedUser,
compatibility);
// TODO(b/71593002): isPrivileged for sharedUser and appInfo should never be out of sync.
// They currently can be if the sharedUser apps are signed with the platform key.
- final boolean isPrivileged = (sharedUserSetting != null)
- ? sharedUserSetting.isPrivileged() | pkg.isPrivileged() : pkg.isPrivileged();
+ final boolean isPrivileged = (sharedUser != null)
+ ? sharedUser.isPrivileged() | pkg.isPrivileged() : pkg.isPrivileged();
return getSeInfo(pkg, isPrivileged, targetSdkVersion);
}
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
index 4345d51..b952f80 100644
--- a/services/core/java/com/android/server/pm/SettingBase.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -88,7 +88,7 @@
/**
* Notify listeners that this object has changed.
*/
- protected void onChanged() {
+ public void onChanged() {
PackageStateMutator.onPackageStateChanged();
dispatchChange(this);
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f21bc93..13a3c5b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1438,19 +1438,19 @@
}
}
- boolean isPermissionUpgradeNeededLPr(int userId) {
+ boolean isPermissionUpgradeNeeded(int userId) {
return mRuntimePermissionsPersistence.isPermissionUpgradeNeeded(userId);
}
- void updateRuntimePermissionsFingerprintLPr(@UserIdInt int userId) {
+ void updateRuntimePermissionsFingerprint(@UserIdInt int userId) {
mRuntimePermissionsPersistence.updateRuntimePermissionsFingerprint(userId);
}
- int getDefaultRuntimePermissionsVersionLPr(int userId) {
+ int getDefaultRuntimePermissionsVersion(int userId) {
return mRuntimePermissionsPersistence.getVersion(userId);
}
- void setDefaultRuntimePermissionsVersionLPr(int version, int userId) {
+ void setDefaultRuntimePermissionsVersion(int version, int userId) {
mRuntimePermissionsPersistence.setVersion(version, userId);
}
@@ -4288,49 +4288,6 @@
return pkg.getCurrentEnabledStateLPr(classNameStr, userId);
}
- boolean setPackageStoppedStateLPw(PackageManagerService pm, String packageName,
- boolean stopped, int userId) {
- final PackageSetting pkgSetting = mPackages.get(packageName);
- if (pkgSetting == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- if (DEBUG_STOPPED) {
- if (stopped) {
- RuntimeException e = new RuntimeException("here");
- e.fillInStackTrace();
- Slog.i(TAG, "Stopping package " + packageName, e);
- }
- }
- if (pkgSetting.getStopped(userId) != stopped) {
- pkgSetting.setStopped(stopped, userId);
- if (pkgSetting.getNotLaunched(userId)) {
- if (pkgSetting.getInstallSource().installerPackageName != null) {
- pm.notifyFirstLaunch(pkgSetting.getPackageName(),
- pkgSetting.getInstallSource().installerPackageName, userId);
- }
- pkgSetting.setNotLaunched(false, userId);
- }
- return true;
- }
- return false;
- }
-
- void setHarmfulAppWarningLPw(String packageName, CharSequence warning, int userId) {
- final PackageSetting pkgSetting = mPackages.get(packageName);
- if (pkgSetting == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- pkgSetting.setHarmfulAppWarning(userId, warning == null ? null : warning.toString());
- }
-
- String getHarmfulAppWarningLPr(String packageName, int userId) {
- final PackageSetting pkgSetting = mPackages.get(packageName);
- if (pkgSetting == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- return pkgSetting.getHarmfulAppWarning(userId);
- }
-
/**
* Returns all users on the device, including pre-created and dying users.
*
@@ -4869,6 +4826,9 @@
date.setTime(pus.getFirstInstallTime());
pw.println(sdf.format(date));
+ pw.print(" uninstallReason=");
+ pw.println(userState.getUninstallReason());
+
if (userState.isSuspended()) {
pw.print(prefix);
pw.println(" Suspend params:");
@@ -5150,7 +5110,7 @@
}
}
- void dumpReadMessagesLPr(PrintWriter pw, DumpState dumpState) {
+ void dumpReadMessages(PrintWriter pw, DumpState dumpState) {
pw.println("Settings parse messages:");
pw.print(mReadMessages.toString());
}
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 2227a78..0638d5e 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -42,12 +42,14 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.SystemConfig;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.Watchable;
@@ -254,6 +256,7 @@
/**
* Given the library name, returns a list of shared libraries on all versions.
+ * TODO: Remove, this is used for live mutation outside of the defined commit path
*/
@GuardedBy("mPm.mLock")
@Override
@@ -262,6 +265,11 @@
return mSharedLibraries.get(libName);
}
+ @VisibleForTesting
+ public WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries() {
+ return mSharedLibraries;
+ }
+
/**
* Returns the shared library with given library name and version number.
*/
@@ -286,18 +294,19 @@
return mStaticLibsByDeclaringPackage.get(declaringPackageName);
}
- @GuardedBy("mPm.mLock")
- private @Nullable PackageSetting getLibraryPackageLPr(@NonNull SharedLibraryInfo libInfo) {
+ @Nullable
+ private PackageStateInternal getLibraryPackage(@NonNull Computer computer,
+ @NonNull SharedLibraryInfo libInfo) {
final VersionedPackage declaringPackage = libInfo.getDeclaringPackage();
if (libInfo.isStatic()) {
// Resolve the package name - we use synthetic package names internally
- final String internalPackageName = mPm.resolveInternalPackageNameLPr(
+ final String internalPackageName = computer.resolveInternalPackageName(
declaringPackage.getPackageName(),
declaringPackage.getLongVersionCode());
- return mPm.mSettings.getPackageLPr(internalPackageName);
+ return computer.getPackageStateInternal(internalPackageName);
}
if (libInfo.isSdk()) {
- return mPm.mSettings.getPackageLPr(declaringPackage.getPackageName());
+ return computer.getPackageStateInternal(declaringPackage.getPackageName());
}
return null;
}
@@ -317,24 +326,26 @@
final StorageManager storage = mInjector.getSystemService(StorageManager.class);
final File volume = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL);
- List<VersionedPackage> packagesToDelete = null;
+ final ArrayList<VersionedPackage> packagesToDelete = new ArrayList<>();
final long now = System.currentTimeMillis();
// Important: We skip shared libs used for some user since
// in such a case we need to keep the APK on the device. The check for
// a lib being used for any user is performed by the uninstall call.
- synchronized (mPm.mLock) {
- final int libCount = mSharedLibraries.size();
+ mPm.executeWithConsistentComputer(computer -> {
+ final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
+ sharedLibraries = computer.getSharedLibraries();
+ final int libCount = sharedLibraries.size();
for (int i = 0; i < libCount; i++) {
final WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
- mSharedLibraries.valueAt(i);
+ sharedLibraries.valueAt(i);
if (versionedLib == null) {
continue;
}
final int versionCount = versionedLib.size();
for (int j = 0; j < versionCount; j++) {
SharedLibraryInfo libInfo = versionedLib.valueAt(j);
- final PackageSetting ps = getLibraryPackageLPr(libInfo);
+ final PackageStateInternal ps = getLibraryPackage(computer, libInfo);
if (ps == null) {
continue;
}
@@ -348,27 +359,22 @@
continue;
}
- if (packagesToDelete == null) {
- packagesToDelete = new ArrayList<>();
- }
packagesToDelete.add(new VersionedPackage(ps.getPkg().getPackageName(),
libInfo.getDeclaringPackage().getLongVersionCode()));
}
}
- }
+ });
- if (packagesToDelete != null) {
- final int packageCount = packagesToDelete.size();
- for (int i = 0; i < packageCount; i++) {
- final VersionedPackage pkgToDelete = packagesToDelete.get(i);
- // Delete the package synchronously (will fail of the lib used for any user).
- if (mDeletePackageHelper.deletePackageX(pkgToDelete.getPackageName(),
- pkgToDelete.getLongVersionCode(), UserHandle.USER_SYSTEM,
- PackageManager.DELETE_ALL_USERS,
- true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) {
- if (volume.getUsableSpace() >= neededSpace) {
- return true;
- }
+ final int packageCount = packagesToDelete.size();
+ for (int i = 0; i < packageCount; i++) {
+ final VersionedPackage pkgToDelete = packagesToDelete.get(i);
+ // Delete the package synchronously (will fail of the lib used for any user).
+ if (mDeletePackageHelper.deletePackageX(pkgToDelete.getPackageName(),
+ pkgToDelete.getLongVersionCode(), UserHandle.USER_SYSTEM,
+ PackageManager.DELETE_ALL_USERS,
+ true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) {
+ if (volume.getUsableSpace() >= neededSpace) {
+ return true;
}
}
}
@@ -525,7 +531,7 @@
@NonNull Map<String, AndroidPackage> availablePackages)
throws PackageManagerException {
final ArrayList<SharedLibraryInfo> sharedLibraryInfos = collectSharedLibraryInfos(
- pkgSetting.getPkg(), availablePackages, null /* newLibraries */);
+ pkg, availablePackages, null /* newLibraries */);
executeSharedLibrariesUpdateLPw(pkg, pkgSetting, changingLib, changingLibSetting,
sharedLibraryInfos, mPm.mUserManager.getUserIds());
}
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesRead.java b/services/core/java/com/android/server/pm/SharedLibrariesRead.java
index e6f2311..84d7478f 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesRead.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesRead.java
@@ -30,7 +30,7 @@
* An interface implemented by {@link SharedLibrariesImpl} for {@link Computer} to get current
* shared libraries on the device.
*/
-interface SharedLibrariesRead {
+public interface SharedLibrariesRead {
/**
* Returns all shared libraries on the device.
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index bc48461..5ef1471 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.content.pm.ApplicationInfo;
+import android.content.pm.SigningDetails;
import com.android.server.pm.pkg.component.ComponentMutateUtils;
import com.android.server.pm.pkg.component.ParsedProcess;
import com.android.server.pm.pkg.component.ParsedProcessImpl;
@@ -28,6 +29,8 @@
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.utils.SnapshotCache;
import libcore.util.EmptyArray;
@@ -40,25 +43,26 @@
/**
* Settings data for a particular shared user ID we know about.
*/
-public final class SharedUserSetting extends SettingBase {
+public final class SharedUserSetting extends SettingBase implements SharedUserApi {
final String name;
int userId;
- // flags that are associated with this uid, regardless of any package flags
+ /** @see SharedUserApi#getUidFlags() **/
int uidFlags;
int uidPrivateFlags;
- // The lowest targetSdkVersion of all apps in the sharedUserSetting, used to assign seinfo so
- // that all apps within the sharedUser run in the same selinux context.
+ /** @see SharedUserApi#getSeInfoTargetSdkVersion() **/
int seInfoTargetSdkVersion;
final ArraySet<PackageSetting> packages;
+ private ArraySet<PackageStateInternal> mPackagesSnapshot;
// It is possible for a system app to leave shared user ID by an update.
// We need to keep track of the shadowed PackageSettings so that it is possible to uninstall
// the update and revert the system app back into the original shared user ID.
final ArraySet<PackageSetting> mDisabledPackages;
+ private ArraySet<PackageStateInternal> mDisabledPackagesSnapshot;
final PackageSignatures signatures = new PackageSignatures();
Boolean signaturesChanged;
@@ -98,7 +102,19 @@
uidFlags = orig.uidFlags;
uidPrivateFlags = orig.uidPrivateFlags;
packages = new ArraySet<>(orig.packages);
+ if (!packages.isEmpty()) {
+ mPackagesSnapshot = new ArraySet<>();
+ for (int index = 0; index < packages.size(); index++) {
+ mPackagesSnapshot.add(new PackageSetting(packages.valueAt(index)));
+ }
+ }
mDisabledPackages = new ArraySet<>(orig.mDisabledPackages);
+ if (!mDisabledPackages.isEmpty()) {
+ mDisabledPackagesSnapshot = new ArraySet<>();
+ for (int index = 0; index < mDisabledPackages.size(); index++) {
+ mDisabledPackagesSnapshot.add(new PackageSetting(mDisabledPackages.valueAt(index)));
+ }
+ }
// A SigningDetails seems to consist solely of final attributes, so
// it is safe to copy the reference.
signatures.mSigningDetails = orig.signatures.mSigningDetails;
@@ -184,10 +200,9 @@
}
}
- /**
- * @return the list of packages that uses this shared UID
- */
- public @NonNull List<AndroidPackage> getPackages() {
+ @NonNull
+ @Override
+ public List<AndroidPackage> getPackages() {
if (packages == null || packages.size() == 0) {
return Collections.emptyList();
}
@@ -201,6 +216,7 @@
return pkgList;
}
+ @Override
public boolean isPrivileged() {
return (this.getPrivateFlags() & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
@@ -291,4 +307,60 @@
onChanged();
return this;
}
+
+ @NonNull
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int getUserId() {
+ return userId;
+ }
+
+ @Override
+ public int getUidFlags() {
+ return uidFlags;
+ }
+
+ @Override
+ public int getPrivateUidFlags() {
+ return uidPrivateFlags;
+ }
+
+ @Override
+ public int getSeInfoTargetSdkVersion() {
+ return seInfoTargetSdkVersion;
+ }
+
+ @NonNull
+ @Override
+ public ArraySet<? extends PackageStateInternal> getPackageStates() {
+ if (mPackagesSnapshot != null) {
+ return mPackagesSnapshot;
+ }
+ return packages;
+ }
+
+ @NonNull
+ @Override
+ public ArraySet<? extends PackageStateInternal> getDisabledPackageStates() {
+ if (mDisabledPackagesSnapshot != null) {
+ return mDisabledPackagesSnapshot;
+ }
+ return mDisabledPackages;
+ }
+
+ @NonNull
+ @Override
+ public SigningDetails getSigningDetails() {
+ return signatures.mSigningDetails;
+ }
+
+ @NonNull
+ @Override
+ public ArrayMap<String, ParsedProcess> getProcesses() {
+ return processes;
+ }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 72db242..bda2589 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1694,15 +1694,6 @@
for (int j = 0; j < shareTargetSize; j++) {
mShareTargets.get(j).saveToXml(out);
}
- synchronized (mLock) {
- final Map<String, ShortcutInfo> copy = mShortcuts;
- if (!mTransientShortcuts.isEmpty()) {
- copy.putAll(mTransientShortcuts);
- mTransientShortcuts.clear();
- }
- saveShortcutsAsync(copy.values().stream().filter(ShortcutInfo::usesQuota).collect(
- Collectors.toList()));
- }
}
out.endTag(null, TAG_ROOT);
@@ -2418,6 +2409,18 @@
})));
}
+ void persistsAllShortcutsAsync() {
+ synchronized (mLock) {
+ final Map<String, ShortcutInfo> copy = mShortcuts;
+ if (!mTransientShortcuts.isEmpty()) {
+ copy.putAll(mTransientShortcuts);
+ mTransientShortcuts.clear();
+ }
+ saveShortcutsAsync(copy.values().stream().filter(ShortcutInfo::usesQuota).collect(
+ Collectors.toList()));
+ }
+ }
+
private void saveShortcutsAsync(
@NonNull final Collection<ShortcutInfo> shortcuts) {
Objects.requireNonNull(shortcuts);
@@ -2489,7 +2492,7 @@
mIsAppSearchSchemaUpToDate = true;
}
} catch (Exception e) {
- Slog.e(TAG, "Failed to invoke app search pkg="
+ Slog.e(TAG, "Failed to create app search session. pkg="
+ getPackageName() + " user=" + mShortcutUser.getUserId(), e);
Objects.requireNonNull(future).completeExceptionally(e);
} finally {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 8393dee..2760578 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -488,7 +488,7 @@
mShortcutBitmapSaver = new ShortcutBitmapSaver(this);
mShortcutDumpFiles = new ShortcutDumpFiles(this);
mIsAppSearchEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, false);
+ SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, true);
if (onlyForPackageManagerApis) {
return; // Don't do anything further. For unit tests only.
@@ -736,7 +736,7 @@
Slog.d(TAG, "unloadUserLocked: user=" + userId);
}
// Save all dirty information.
- saveDirtyInfo();
+ saveDirtyInfo(false);
// Unload
mUsers.delete(userId);
@@ -1191,6 +1191,10 @@
@VisibleForTesting
void saveDirtyInfo() {
+ saveDirtyInfo(true);
+ }
+
+ private void saveDirtyInfo(boolean saveShortcutsInAppSearch) {
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "saveDirtyInfo");
}
@@ -1205,6 +1209,10 @@
if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
saveBaseStateLocked();
} else {
+ if (saveShortcutsInAppSearch) {
+ getUserShortcutsLocked(userId).forAllPackages(
+ ShortcutPackage::persistsAllShortcutsAsync);
+ }
saveUserLocked(userId);
}
}
@@ -3719,13 +3727,16 @@
private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ if (DEBUG || DEBUG_REBOOT) {
+ Slog.d(TAG, "Shutdown broadcast received.");
+ }
// Since it cleans up the shortcut directory and rewrite the ShortcutPackageItems
// in odrder during saveToXml(), it could lead to shortcuts missing when shutdown.
// We need it so that it can finish up saving before shutdown.
synchronized (mLock) {
if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) {
mHandler.removeCallbacks(mSaveDirtyInfoRunner);
- saveDirtyInfo();
+ saveDirtyInfo(false);
}
mShutdown.set(true);
}
@@ -4394,7 +4405,7 @@
// Save to the filesystem.
scheduleSaveUser(userId);
- saveDirtyInfo();
+ saveDirtyInfo(false);
// Note, in case of backup, we don't have to wait on bitmap saving, because we don't
// back up bitmaps anyway.
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index f466ca7..1ea8b24 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -46,14 +46,17 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.SuspendParams;
+import com.android.server.pm.pkg.mutate.PackageUserStateWrite;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.function.Predicate;
public final class SuspendPackageHelper {
@@ -107,57 +110,87 @@
return packageNames;
}
+ final SuspendParams newSuspendParams =
+ SuspendParams.getInstanceOrNull(dialogInfo, appExtras, launcherExtras);
+
final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
final IntArray changedUids = new IntArray(packageNames.length);
- final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length);
final IntArray modifiedUids = new IntArray(packageNames.length);
- final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
- final boolean[] canSuspend =
- suspended ? canSuspendPackageForUser(packageNames, userId, callingUid) : null;
+ final List<String> unmodifiablePackages = new ArrayList<>(packageNames.length);
- for (int i = 0; i < packageNames.length; i++) {
- final String packageName = packageNames[i];
- if (callingPackage.equals(packageName)) {
- Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
- + (suspended ? "" : "un") + "suspend itself. Ignoring");
- unactionedPackages.add(packageName);
- continue;
- }
- final PackageSetting pkgSetting;
- synchronized (mPm.mLock) {
- pkgSetting = mPm.mSettings.getPackageLPr(packageName);
- if (pkgSetting == null
- || mPm.shouldFilterApplication(pkgSetting, callingUid, userId)) {
- Slog.w(TAG, "Could not find package setting for package: " + packageName
- + ". Skipping suspending/un-suspending.");
- unactionedPackages.add(packageName);
+ ArraySet<String> modifiedPackages = new ArraySet<>();
+
+ mPm.executeWithConsistentComputer(computer -> {
+ final boolean[] canSuspend = suspended
+ ? canSuspendPackageForUser(computer, packageNames, userId, callingUid) : null;
+ for (int i = 0; i < packageNames.length; i++) {
+ final String packageName = packageNames[i];
+ if (callingPackage.equals(packageName)) {
+ Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
+ + (suspended ? "" : "un") + "suspend itself. Ignoring");
+ unmodifiablePackages.add(packageName);
continue;
}
- }
- if (canSuspend != null && !canSuspend[i]) {
- unactionedPackages.add(packageName);
- continue;
- }
- final boolean packageUnsuspended;
- final boolean packageModified;
- synchronized (mPm.mLock) {
- if (suspended) {
- packageModified = pkgSetting.addOrUpdateSuspension(callingPackage,
- dialogInfo, appExtras, launcherExtras, userId);
- } else {
- packageModified = pkgSetting.removeSuspension(callingPackage, userId);
+ final PackageStateInternal packageState =
+ computer.getPackageStateInternal(packageName);
+ if (packageState == null
+ || computer.shouldFilterApplication(packageState, callingUid, userId)) {
+ Slog.w(TAG, "Could not find package setting for package: " + packageName
+ + ". Skipping suspending/un-suspending.");
+ unmodifiablePackages.add(packageName);
+ continue;
}
- packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId);
+ if (canSuspend != null && !canSuspend[i]) {
+ unmodifiablePackages.add(packageName);
+ continue;
+ }
+
+ final ArrayMap<String, SuspendParams> suspendParamsMap =
+ packageState.getUserStateOrDefault(userId).getSuspendParams();
+ final SuspendParams suspendParams = suspendParamsMap == null
+ ? null : suspendParamsMap.get(packageName);
+ boolean hasSuspension = suspendParams != null;
+ if (suspended) {
+ if (hasSuspension) {
+ // Skip if there's no changes
+ if (Objects.equals(suspendParams.getDialogInfo(), dialogInfo)
+ && Objects.equals(suspendParams.getAppExtras(), appExtras)
+ && Objects.equals(suspendParams.getLauncherExtras(),
+ launcherExtras)) {
+ // Carried over API behavior, must notify change even if no change
+ changedPackagesList.add(packageName);
+ changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ continue;
+ }
+ }
+ }
+
+ // If size one, the package will be unsuspended from this call
+ boolean packageUnsuspended =
+ !suspended && CollectionUtils.size(suspendParamsMap) <= 1;
+ if (suspended || packageUnsuspended) {
+ changedPackagesList.add(packageName);
+ changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ }
+
+ modifiedPackages.add(packageName);
+ modifiedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
}
- if (suspended || packageUnsuspended) {
- changedPackagesList.add(packageName);
- changedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
+ });
+
+ mPm.commitPackageStateMutation(null, mutator -> {
+ final int size = modifiedPackages.size();
+ for (int index = 0; index < size; index++) {
+ final String packageName = modifiedPackages.valueAt(index);
+ final PackageUserStateWrite userState = mutator.forPackage(packageName)
+ .userState(userId);
+ if (suspended) {
+ userState.putSuspendParams(callingPackage, newSuspendParams);
+ } else {
+ userState.removeSuspension(callingPackage);
+ }
}
- if (packageModified) {
- modifiedPackagesList.add(packageName);
- modifiedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
- }
- }
+ });
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(new String[0]);
@@ -166,16 +199,14 @@
: Intent.ACTION_PACKAGES_UNSUSPENDED,
changedPackages, changedUids.toArray(), userId);
sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
- synchronized (mPm.mLock) {
- mPm.scheduleWritePackageRestrictionsLocked(userId);
- }
+ mPm.scheduleWritePackageRestrictions(userId);
}
// Send the suspension changed broadcast to ensure suspension state is not stale.
- if (!modifiedPackagesList.isEmpty()) {
+ if (!modifiedPackages.isEmpty()) {
sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
- modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId);
+ modifiedPackages.toArray(new String[0]), modifiedUids.toArray(), userId);
}
- return unactionedPackages.toArray(new String[0]);
+ return unmodifiablePackages.toArray(new String[0]);
}
/**
@@ -194,20 +225,22 @@
return packageNames;
}
final ArraySet<String> unactionablePackages = new ArraySet<>();
- final boolean[] canSuspend = canSuspendPackageForUser(packageNames, userId, callingUid);
- for (int i = 0; i < packageNames.length; i++) {
- if (!canSuspend[i]) {
- unactionablePackages.add(packageNames[i]);
- continue;
- }
- synchronized (mPm.mLock) {
- final PackageSetting ps = mPm.mSettings.getPackageLPr(packageNames[i]);
- if (ps == null || mPm.shouldFilterApplication(ps, callingUid, userId)) {
+ mPm.executeWithConsistentComputer(computer -> {
+ final boolean[] canSuspend = canSuspendPackageForUser(computer, packageNames, userId,
+ callingUid);
+ for (int i = 0; i < packageNames.length; i++) {
+ if (!canSuspend[i]) {
+ unactionablePackages.add(packageNames[i]);
+ continue;
+ }
+ final PackageStateInternal packageState =
+ computer.getPackageStateFiltered(packageNames[i], callingUid, userId);
+ if (packageState == null) {
Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
unactionablePackages.add(packageNames[i]);
}
}
- }
+ });
return unactionablePackages.toArray(new String[unactionablePackages.size()]);
}
@@ -253,19 +286,53 @@
@NonNull Predicate<String> suspendingPackagePredicate, int userId) {
final List<String> unsuspendedPackages = new ArrayList<>();
final IntArray unsuspendedUids = new IntArray();
- synchronized (mPm.mLock) {
+ final ArrayMap<String, ArraySet<String>> pkgToSuspendingPkgsToCommit = new ArrayMap<>();
+ mPm.executeWithConsistentComputer(computer -> {
for (String packageName : packagesToChange) {
- final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
- if (ps != null && ps.getUserStateOrDefault(userId).isSuspended()) {
- ps.removeSuspension(suspendingPackagePredicate, userId);
- if (!ps.getUserStateOrDefault(userId).isSuspended()) {
- unsuspendedPackages.add(ps.getPackageName());
- unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId()));
+ final PackageStateInternal packageState =
+ computer.getPackageStateInternal(packageName);
+ final PackageUserStateInternal packageUserState = packageState == null
+ ? null : packageState.getUserStateOrDefault(userId);
+ if (packageUserState == null || !packageUserState.isSuspended()) {
+ continue;
+ }
+
+ ArrayMap<String, SuspendParams> suspendParamsMap = packageUserState.getSuspendParams();
+ int countRemoved = 0;
+ for (int index = 0; index < suspendParamsMap.size(); index++) {
+ String suspendingPackage = suspendParamsMap.keyAt(index);
+ if (suspendingPackagePredicate.test(suspendingPackage)) {
+ ArraySet<String> suspendingPkgsToCommit =
+ pkgToSuspendingPkgsToCommit.get(packageName);
+ if (suspendingPkgsToCommit == null) {
+ suspendingPkgsToCommit = new ArraySet<>();
+ pkgToSuspendingPkgsToCommit.put(packageName, suspendingPkgsToCommit);
+ }
+ suspendingPkgsToCommit.add(suspendingPackage);
+ countRemoved++;
}
}
+
+ // Everything would be removed and package unsuspended
+ if (countRemoved == suspendParamsMap.size()) {
+ unsuspendedPackages.add(packageState.getPackageName());
+ unsuspendedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ }
}
- mPm.scheduleWritePackageRestrictionsLocked(userId);
- }
+ });
+
+ mPm.commitPackageStateMutation(null, mutator -> {
+ for (int mapIndex = 0; mapIndex < pkgToSuspendingPkgsToCommit.size(); mapIndex++) {
+ String packageName = pkgToSuspendingPkgsToCommit.keyAt(mapIndex);
+ ArraySet<String> packagesToRemove = pkgToSuspendingPkgsToCommit.valueAt(mapIndex);
+ PackageUserStateWrite userState = mutator.forPackage(packageName).userState(userId);
+ for (int setIndex = 0; setIndex < packagesToRemove.size(); setIndex++) {
+ userState.removeSuspension(packagesToRemove.valueAt(setIndex));
+ }
+ }
+ });
+
+ mPm.scheduleWritePackageRestrictions(userId);
if (!unsuspendedPackages.isEmpty()) {
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
@@ -406,7 +473,8 @@
* @return An array containing results of the checks
*/
@NonNull
- boolean[] canSuspendPackageForUser(@NonNull String[] packageNames, int userId, int callingUid) {
+ boolean[] canSuspendPackageForUser(@NonNull Computer computer, @NonNull String[] packageNames,
+ int userId, int callingUid) {
final boolean[] canSuspend = new boolean[packageNames.length];
final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId, callingUid);
final long token = Binder.clearCallingIdentity();
@@ -459,36 +527,38 @@
+ "\": required for permissions management");
continue;
}
- synchronized (mPm.mLock) {
- if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
- Slog.w(TAG, "Cannot suspend package \"" + packageName
- + "\": protected package");
- continue;
- }
- if (!isCallerOwner && mPm.mSettings.getBlockUninstallLPr(userId, packageName)) {
- Slog.w(TAG, "Cannot suspend package \"" + packageName
- + "\": blocked by admin");
- continue;
- }
+ if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
+ Slog.w(TAG, "Cannot suspend package \"" + packageName
+ + "\": protected package");
+ continue;
+ }
+ if (!isCallerOwner && computer.getBlockUninstall(userId, packageName)) {
+ Slog.w(TAG, "Cannot suspend package \"" + packageName
+ + "\": blocked by admin");
+ continue;
+ }
- AndroidPackage pkg = mPm.mPackages.get(packageName);
- if (pkg != null) {
- // Cannot suspend SDK libs as they are controlled by SDK manager.
- if (pkg.isSdkLibrary()) {
- Slog.w(TAG, "Cannot suspend package: " + packageName
- + " providing SDK library: "
- + pkg.getSdkLibName());
- continue;
- }
- // Cannot suspend static shared libs as they are considered
- // a part of the using app (emulating static linking). Also
- // static libs are installed always on internal storage.
- if (pkg.isStaticSharedLibrary()) {
- Slog.w(TAG, "Cannot suspend package: " + packageName
- + " providing static shared library: "
- + pkg.getStaticSharedLibName());
- continue;
- }
+ // Cannot suspend static shared libs as they are considered
+ // a part of the using app (emulating static linking). Also
+ // static libs are installed always on internal storage.
+ PackageStateInternal packageState = computer.getPackageStateInternal(packageName);
+ AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
+ if (pkg != null) {
+ // Cannot suspend SDK libs as they are controlled by SDK manager.
+ if (pkg.isSdkLibrary()) {
+ Slog.w(TAG, "Cannot suspend package: " + packageName
+ + " providing SDK library: "
+ + pkg.getSdkLibName());
+ continue;
+ }
+ // Cannot suspend static shared libs as they are considered
+ // a part of the using app (emulating static linking). Also
+ // static libs are installed always on internal storage.
+ if (pkg.isStaticSharedLibrary()) {
+ Slog.w(TAG, "Cannot suspend package: " + packageName
+ + " providing static shared library: "
+ + pkg.getStaticSharedLibName());
+ continue;
}
}
if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index a1a6f5a..9fb1f8f 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -30,11 +30,13 @@
import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
import android.util.Slog;
+import android.util.SparseArrayMap;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -211,31 +213,66 @@
Slog.i(TAG, "Reviewing whitelisted packages due to "
+ (isFirstBoot ? "[firstBoot]" : "") + (isConsideredUpgrade ? "[upgrade]" : ""));
final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+
+ // User ID -> package name -> installed
+ SparseArrayMap<String, Boolean> changesToCommit = new SparseArrayMap<>();
+
// Install/uninstall system packages per user.
for (int userId : mUm.getUserIds()) {
final Set<String> userWhitelist = getInstallablePackagesForUserId(userId);
- pmInt.forEachPackageSetting(pkgSetting -> {
- AndroidPackage pkg = pkgSetting.getPkg();
- if (pkg == null || !pkg.isSystem()) {
- return;
+
+ // If null, run for all packages
+ if (userWhitelist == null) {
+ pmInt.forEachPackageState(packageState -> {
+ if (packageState.getPkg() == null) {
+ return;
+ }
+ final boolean install = !packageState.getTransientState()
+ .isHiddenUntilInstalled();
+ if (packageState.getUserStateOrDefault(userId).isInstalled() != install
+ && shouldChangeInstallationState(packageState, install, userId,
+ isFirstBoot, isConsideredUpgrade, preExistingPackages)) {
+ changesToCommit.add(userId, packageState.getPackageName(), install);
+ }
+ });
+ } else {
+ for (String packageName : userWhitelist) {
+ PackageStateInternal packageState = pmInt.getPackageStateInternal(packageName);
+ if (packageState.getPkg() == null) {
+ continue;
+ }
+
+ final boolean install = !packageState.getTransientState()
+ .isHiddenUntilInstalled();
+ if (packageState.getUserStateOrDefault(userId).isInstalled() != install
+ && shouldChangeInstallationState(packageState, install, userId,
+ isFirstBoot, isConsideredUpgrade, preExistingPackages)) {
+ changesToCommit.add(userId, packageState.getPackageName(), install);
+ }
}
- final boolean install =
- (userWhitelist == null || userWhitelist.contains(pkg.getPackageName()))
- && !pkgSetting.getPkgState().isHiddenUntilInstalled();
- if (pkgSetting.getInstalled(userId) == install
- || !shouldChangeInstallationState(pkgSetting, install, userId, isFirstBoot,
- isConsideredUpgrade, preExistingPackages)) {
- return;
- }
- pkgSetting.setInstalled(install, userId);
- pkgSetting.setUninstallReason(
- install ? PackageManager.UNINSTALL_REASON_UNKNOWN :
- PackageManager.UNINSTALL_REASON_USER_TYPE,
- userId);
- Slog.i(TAG, (install ? "Installed " : "Uninstalled ")
- + pkg.getPackageName() + " for user " + userId);
- });
+ }
}
+
+ pmInt.commitPackageStateMutation(null, packageStateMutator -> {
+ for (int userIndex = 0; userIndex < changesToCommit.numMaps(); userIndex++) {
+ int userId = changesToCommit.keyAt(userIndex);
+ int packagesSize = changesToCommit.numElementsForKey(userId);
+ for (int packageIndex = 0; packageIndex < packagesSize; ++packageIndex) {
+ String packageName = changesToCommit.keyAt(userIndex, packageIndex);
+ boolean installed = changesToCommit.valueAt(userIndex, packageIndex);
+ packageStateMutator.forPackage(packageName)
+ .userState(userId)
+ .setInstalled(installed)
+ .setUninstallReason(installed
+ ? PackageManager.UNINSTALL_REASON_UNKNOWN
+ : PackageManager.UNINSTALL_REASON_USER_TYPE);
+
+ Slog.i(TAG + "CommitDebug", (installed ? "Installed " : "Uninstalled ")
+ + packageName + " for user " + userId);
+ }
+ }
+ });
+
return true;
}
@@ -250,7 +287,7 @@
* @param preOtaPkgs list of packages on the device prior to the upgrade.
* Cannot be null if isUpgrade is true.
*/
- private static boolean shouldChangeInstallationState(PackageSetting pkgSetting,
+ private static boolean shouldChangeInstallationState(PackageStateInternal packageState,
boolean install,
@UserIdInt int userId,
boolean isFirstBoot,
@@ -258,11 +295,12 @@
@Nullable ArraySet<String> preOtaPkgs) {
if (install) {
// Only proceed with install if we are the only reason why it had been uninstalled.
- return pkgSetting.getUninstallReason(userId)
+ return packageState.getUserStateOrDefault(userId).getUninstallReason()
== PackageManager.UNINSTALL_REASON_USER_TYPE;
} else {
// Only proceed with uninstall if the package is new to the device.
- return isFirstBoot || (isUpgrade && !preOtaPkgs.contains(pkgSetting.getPackageName()));
+ return isFirstBoot
+ || (isUpgrade && !preOtaPkgs.contains(packageState.getPackageName()));
}
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 2d0a3ef..63469cb 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -34,19 +34,6 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.SharedLibraryInfo;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
-import com.android.server.pm.pkg.component.ComponentParseUtils;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
-import com.android.server.pm.pkg.component.ParsedProcess;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedService;
-import com.android.server.pm.pkg.PackageUserStateUtils;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -60,6 +47,19 @@
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUnserialized;
import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.PackageUserStateUtils;
+import com.android.server.pm.pkg.component.ComponentParseUtils;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedComponent;
+import com.android.server.pm.pkg.component.ParsedInstrumentation;
+import com.android.server.pm.pkg.component.ParsedMainComponent;
+import com.android.server.pm.pkg.component.ParsedPermission;
+import com.android.server.pm.pkg.component.ParsedPermissionGroup;
+import com.android.server.pm.pkg.component.ParsedProcess;
+import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedService;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
import libcore.util.EmptyArray;
diff --git a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
index 564585b..97d526d 100644
--- a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
@@ -18,10 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import com.android.server.pm.pkg.component.ParsedComponent;
import android.util.Pair;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.component.ParsedComponent;
/**
* For exposing internal fields to the rest of the server, enforcing that any overridden state from
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 8e41c9b..db9c1b5 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -4486,7 +4486,7 @@
@Override
public void readLegacyPermissionStateTEMP() {
final int[] userIds = getAllUserIds();
- mPackageManagerInt.forEachPackageSetting(ps -> {
+ mPackageManagerInt.forEachPackageState(ps -> {
final int appId = ps.getAppId();
final LegacyPermissionState legacyState = ps.getLegacyPermissionState();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
index 56f62ab..fb2fe1f 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java
@@ -24,7 +24,6 @@
import com.android.server.pm.InstallSource;
import com.android.server.pm.PackageKeySetData;
-import com.android.server.pm.SharedUserSetting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.LegacyPermissionState;
@@ -52,7 +51,7 @@
InstallSource getInstallSource();
@Nullable
- SharedUserSetting getSharedUser();
+ SharedUserApi getSharedUser();
// TODO: Remove this in favor of boolean APIs
int getFlags();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
index ef21543..7bd720a 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
@@ -40,6 +40,7 @@
* where they would be lost implicitly by re-generating the package object.
*/
@DataClass(genSetters = true, genConstructor = false, genBuilder = false)
+@DataClass.Suppress({"setLastPackageUsageTimeInMills", "setPackageSetting"})
public class PackageStateUnserialized {
private boolean hiddenUntilInstalled;
@@ -58,6 +59,14 @@
@Nullable
private String overrideSeInfo;
+ // TODO: Remove in favor of finer grained change notification
+ @NonNull
+ private final PackageSetting mPackageSetting;
+
+ public PackageStateUnserialized(@NonNull PackageSetting packageSetting) {
+ mPackageSetting = packageSetting;
+ }
+
private long[] lazyInitLastPackageUsageTimeInMills() {
return new long[PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT];
}
@@ -70,6 +79,7 @@
return this;
}
getLastPackageUsageTimeInMills()[reason] = time;
+ mPackageSetting.onChanged();
return this;
}
@@ -108,6 +118,7 @@
this.updatedSystemApp = other.updatedSystemApp;
this.lastPackageUsageTimeInMills = other.lastPackageUsageTimeInMills;
this.overrideSeInfo = other.overrideSeInfo;
+ mPackageSetting.onChanged();
}
public @NonNull List<SharedLibraryInfo> getNonNativeUsesLibraryInfos() {
@@ -115,8 +126,45 @@
.filter((l) -> !l.isNative()).collect(Collectors.toList());
}
+ public PackageStateUnserialized setHiddenUntilInstalled(boolean value) {
+ hiddenUntilInstalled = value;
+ mPackageSetting.onChanged();
+ return this;
+ }
- // Code below generated by codegen v1.0.14.
+ public PackageStateUnserialized setUsesLibraryInfos(@NonNull List<SharedLibraryInfo> value) {
+ usesLibraryInfos = value;
+ mPackageSetting.onChanged();
+ return this;
+ }
+
+ public PackageStateUnserialized setUsesLibraryFiles(@NonNull List<String> value) {
+ usesLibraryFiles = value;
+ mPackageSetting.onChanged();
+ return this;
+ }
+
+ public PackageStateUnserialized setUpdatedSystemApp(boolean value) {
+ updatedSystemApp = value;
+ mPackageSetting.onChanged();
+ return this;
+ }
+
+ public PackageStateUnserialized setLastPackageUsageTimeInMills(@NonNull long... value) {
+ lastPackageUsageTimeInMills = value;
+ mPackageSetting.onChanged();
+ return this;
+ }
+
+ public PackageStateUnserialized setOverrideSeInfo(@Nullable String value) {
+ overrideSeInfo = value;
+ mPackageSetting.onChanged();
+ return this;
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -169,52 +217,15 @@
}
@DataClass.Generated.Member
- public PackageStateUnserialized setHiddenUntilInstalled(boolean value) {
- hiddenUntilInstalled = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public PackageStateUnserialized setUsesLibraryInfos(@NonNull List<SharedLibraryInfo> value) {
- usesLibraryInfos = value;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, usesLibraryInfos);
- return this;
- }
-
- @DataClass.Generated.Member
- public PackageStateUnserialized setUsesLibraryFiles(@NonNull List<String> value) {
- usesLibraryFiles = value;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, usesLibraryFiles);
- return this;
- }
-
- @DataClass.Generated.Member
- public PackageStateUnserialized setUpdatedSystemApp(boolean value) {
- updatedSystemApp = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public PackageStateUnserialized setLastPackageUsageTimeInMills(@NonNull long... value) {
- lastPackageUsageTimeInMills = value;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, lastPackageUsageTimeInMills);
- return this;
- }
-
- @DataClass.Generated.Member
- public PackageStateUnserialized setOverrideSeInfo(@Nullable String value) {
- overrideSeInfo = value;
- return this;
+ public @NonNull PackageSetting getPackageSetting() {
+ return mPackageSetting;
}
@DataClass.Generated(
- time = 1580422870209L,
- codegenVersion = "1.0.14",
+ time = 1642554781099L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java",
- inputSignatures = "private boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate boolean updatedSystemApp\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\n @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate long[] lazyInitLastPackageUsageTimeInMills()\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic long getLatestPackageUseTimeInMills()\npublic long getLatestForegroundPackageUseTimeInMills()\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
+ inputSignatures = "private boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate boolean updatedSystemApp\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\nprivate long[] lazyInitLastPackageUsageTimeInMills()\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic long getLatestPackageUseTimeInMills()\npublic long getLatestForegroundPackageUseTimeInMills()\npublic void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java
new file mode 100644
index 0000000..43eac53
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.pm.SigningDetails;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedProcess;
+
+import java.util.List;
+
+public interface SharedUserApi {
+
+ @NonNull
+ String getName();
+
+ @UserIdInt
+ int getUserId();
+
+ // flags that are associated with this uid, regardless of any package flags
+ int getUidFlags();
+ int getPrivateUidFlags();
+
+ // The lowest targetSdkVersion of all apps in the sharedUserSetting, used to assign seinfo so
+ // that all apps within the sharedUser run in the same selinux context.
+ int getSeInfoTargetSdkVersion();
+
+ /**
+ * @return the list of packages that uses this shared UID
+ */
+ @NonNull
+ List<AndroidPackage> getPackages();
+
+ @NonNull
+ ArraySet<? extends PackageStateInternal> getPackageStates();
+
+ // It is possible for a system app to leave shared user ID by an update.
+ // We need to keep track of the shadowed PackageSettings so that it is possible to uninstall
+ // the update and revert the system app back into the original shared user ID.
+ @NonNull
+ ArraySet<? extends PackageStateInternal> getDisabledPackageStates();
+
+ @NonNull
+ SigningDetails getSigningDetails();
+
+ @NonNull
+ ArrayMap<String, ParsedProcess> getProcesses();
+
+ boolean isPrivileged();
+}
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 35d4d9e..951ddfa 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.overlay.OverlayPaths;
@@ -177,6 +178,13 @@
}
@Override
+ public void onChanged() {
+ if (mState != null) {
+ mState.onChanged();
+ }
+ }
+
+ @Override
public PackageStateWrite setLastPackageUsageTime(int reason, long timeInMillis) {
if (mState != null) {
mState.getTransientState().setLastPackageUsageTimeInMills(reason, timeInMillis);
@@ -217,6 +225,51 @@
return this;
}
+ @NonNull
+ @Override
+ public PackageStateWrite setCategoryOverride(@ApplicationInfo.Category int category) {
+ if (mState != null) {
+ mState.setCategoryOverride(category);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PackageStateWrite setUpdateAvailable(boolean updateAvailable) {
+ if (mState != null) {
+ mState.setUpdateAvailable(updateAvailable);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PackageStateWrite setLoadingProgress(float progress) {
+ if (mState != null) {
+ mState.setLoadingProgress(progress);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PackageStateWrite setOverrideSeInfo(@Nullable String newSeInfo) {
+ if (mState != null) {
+ mState.getTransientState().setOverrideSeInfo(newSeInfo);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PackageStateWrite setInstaller(@NonNull String installerPackageName) {
+ if (mState != null) {
+ mState.setInstallerPackageName(installerPackageName);
+ }
+ return this;
+ }
+
private static class UserStateWriteWrapper implements PackageUserStateWrite {
@Nullable
@@ -328,6 +381,25 @@
}
return this;
}
+
+ @NonNull
+ @Override
+ public PackageUserStateWrite setSplashScreenTheme(@Nullable String theme) {
+ if (mUserState != null) {
+ mUserState.setSplashScreenTheme(theme);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public PackageUserStateWrite setComponentLabelIcon(@NonNull ComponentName componentName,
+ @Nullable String nonLocalizedLabel, @Nullable Integer icon) {
+ if (mUserState != null) {
+ mUserState.overrideLabelAndIcon(componentName, nonLocalizedLabel, icon);
+ }
+ return null;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
index 585bece..1ac0b05 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
@@ -17,12 +17,16 @@
package com.android.server.pm.pkg.mutate;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.util.ArraySet;
public interface PackageStateWrite {
+ void onChanged();
+
@NonNull
PackageUserStateWrite userState(@UserIdInt int userId);
@@ -38,4 +42,19 @@
@NonNull
PackageStateWrite setMimeGroup(@NonNull String mimeGroup, @NonNull ArraySet<String> mimeTypes);
+
+ @NonNull
+ PackageStateWrite setCategoryOverride(@ApplicationInfo.Category int category);
+
+ @NonNull
+ PackageStateWrite setUpdateAvailable(boolean updateAvailable);
+
+ @NonNull
+ PackageStateWrite setLoadingProgress(float progress);
+
+ @NonNull
+ PackageStateWrite setOverrideSeInfo(@Nullable String newSeInfo);
+
+ @NonNull
+ PackageStateWrite setInstaller(@NonNull String installerPackageName);
}
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
index e23a1b6..11d6d97 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.overlay.OverlayPaths;
@@ -60,4 +61,11 @@
@NonNull
PackageUserStateWrite setHarmfulAppWarning(@Nullable String warning);
+
+ @NonNull
+ PackageUserStateWrite setSplashScreenTheme(@Nullable String theme);
+
+ @NonNull
+ PackageUserStateWrite setComponentLabelIcon(@NonNull ComponentName componentName,
+ @Nullable String nonLocalizedLabel, @Nullable Integer icon);
}
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/sensorprivacy/AllSensorStateController.java b/services/core/java/com/android/server/sensorprivacy/AllSensorStateController.java
new file mode 100644
index 0000000..f797f09
--- /dev/null
+++ b/services/core/java/com/android/server/sensorprivacy/AllSensorStateController.java
@@ -0,0 +1,155 @@
+/*
+ * 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.sensorprivacy;
+
+import android.annotation.NonNull;
+import android.os.Environment;
+import android.os.Handler;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.IoThread;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Objects;
+
+class AllSensorStateController {
+
+ private static final String LOG_TAG = AllSensorStateController.class.getSimpleName();
+
+ private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml";
+ private static final String XML_TAG_SENSOR_PRIVACY = "all-sensor-privacy";
+ private static final String XML_TAG_SENSOR_PRIVACY_LEGACY = "sensor-privacy";
+ private static final String XML_ATTRIBUTE_ENABLED = "enabled";
+
+ private static AllSensorStateController sInstance;
+
+ private final AtomicFile mAtomicFile =
+ new AtomicFile(new File(Environment.getDataSystemDirectory(), SENSOR_PRIVACY_XML_FILE));
+
+ private boolean mEnabled;
+ private SensorPrivacyStateController.AllSensorPrivacyListener mListener;
+ private Handler mListenerHandler;
+
+ static AllSensorStateController getInstance() {
+ if (sInstance == null) {
+ sInstance = new AllSensorStateController();
+ }
+ return sInstance;
+ }
+
+ private AllSensorStateController() {
+ if (!mAtomicFile.exists()) {
+ return;
+ }
+ try (FileInputStream inputStream = mAtomicFile.openRead()) {
+ TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
+
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (XML_TAG_SENSOR_PRIVACY.equals(tagName)) {
+ mEnabled |= XmlUtils
+ .readBooleanAttribute(parser, XML_ATTRIBUTE_ENABLED, false);
+ break;
+ }
+ if (XML_TAG_SENSOR_PRIVACY_LEGACY.equals(tagName)) {
+ mEnabled |= XmlUtils
+ .readBooleanAttribute(parser, XML_ATTRIBUTE_ENABLED, false);
+ }
+ if ("user".equals(tagName)) { // Migrate from mic/cam toggles format
+ int user = XmlUtils.readIntAttribute(parser, "id", -1);
+ if (user == 0) {
+ mEnabled |=
+ XmlUtils.readBooleanAttribute(parser, XML_ATTRIBUTE_ENABLED);
+ }
+ }
+ XmlUtils.nextElement(parser);
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(LOG_TAG, "Caught an exception reading the state from storage: ", e);
+ mEnabled = false;
+ }
+ }
+
+ public boolean getAllSensorStateLocked() {
+ return mEnabled;
+ }
+
+ public void setAllSensorStateLocked(boolean enabled) {
+ if (mEnabled != enabled) {
+ mEnabled = enabled;
+ if (mListener != null && mListenerHandler != null) {
+ mListenerHandler.sendMessage(
+ PooledLambda.obtainMessage(mListener::onAllSensorPrivacyChanged, enabled));
+ }
+ }
+ }
+
+ void setAllSensorPrivacyListenerLocked(Handler handler,
+ SensorPrivacyStateController.AllSensorPrivacyListener listener) {
+ Objects.requireNonNull(handler);
+ Objects.requireNonNull(listener);
+ if (mListener != null) {
+ throw new IllegalStateException("Listener is already set");
+ }
+ mListener = listener;
+ mListenerHandler = handler;
+ }
+
+ public void schedulePersistLocked() {
+ IoThread.getHandler().sendMessage(PooledLambda.obtainMessage(this::persist, mEnabled));
+ }
+
+ private void persist(boolean enabled) {
+ FileOutputStream outputStream = null;
+ try {
+ outputStream = mAtomicFile.startWrite();
+ TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
+ serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled);
+ serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
+ serializer.endDocument();
+ mAtomicFile.finishWrite(outputStream);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Caught an exception persisting the sensor privacy state: ", e);
+ mAtomicFile.failWrite(outputStream);
+ }
+ }
+
+ void resetForTesting() {
+ mListener = null;
+ mListenerHandler = null;
+ mEnabled = false;
+ }
+
+ void dumpLocked(@NonNull DualDumpOutputStream dumpStream) {
+ // TODO stub
+ }
+}
diff --git a/services/core/java/com/android/server/sensorprivacy/OWNERS b/services/core/java/com/android/server/sensorprivacy/OWNERS
new file mode 100644
index 0000000..15e3f7a
--- /dev/null
+++ b/services/core/java/com/android/server/sensorprivacy/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/native:/libs/sensorprivacy/OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/sensorprivacy/PersistedState.java b/services/core/java/com/android/server/sensorprivacy/PersistedState.java
new file mode 100644
index 0000000..ce9fff5
--- /dev/null
+++ b/services/core/java/com/android/server/sensorprivacy/PersistedState.java
@@ -0,0 +1,494 @@
+/*
+ * 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.sensorprivacy;
+
+import android.hardware.SensorPrivacyManager;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.service.SensorPrivacyIndividualEnabledSensorProto;
+import android.service.SensorPrivacySensorProto;
+import android.service.SensorPrivacyServiceDumpProto;
+import android.service.SensorPrivacyUserProto;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.internal.util.function.QuadConsumer;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.IoThread;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Class for managing persisted state. Synchronization must be handled by the caller.
+ */
+class PersistedState {
+
+ private static final String LOG_TAG = PersistedState.class.getSimpleName();
+
+ /** Version number indicating compatibility parsing the persisted file */
+ private static final int CURRENT_PERSISTENCE_VERSION = 2;
+ /** Version number indicating the persisted data needs upgraded to match new internal data
+ * structures and features */
+ private static final int CURRENT_VERSION = 2;
+
+ private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy";
+ private static final String XML_TAG_SENSOR_STATE = "sensor-state";
+ private static final String XML_ATTRIBUTE_PERSISTENCE_VERSION = "persistence-version";
+ private static final String XML_ATTRIBUTE_VERSION = "version";
+ private static final String XML_ATTRIBUTE_TOGGLE_TYPE = "toggle-type";
+ private static final String XML_ATTRIBUTE_USER_ID = "user-id";
+ private static final String XML_ATTRIBUTE_SENSOR = "sensor";
+ private static final String XML_ATTRIBUTE_STATE_TYPE = "state-type";
+ private static final String XML_ATTRIBUTE_LAST_CHANGE = "last-change";
+
+ private final AtomicFile mAtomicFile;
+
+ private ArrayMap<TypeUserSensor, SensorState> mStates = new ArrayMap<>();
+
+ static PersistedState fromFile(String fileName) {
+ return new PersistedState(fileName);
+ }
+
+ private PersistedState(String fileName) {
+ mAtomicFile = new AtomicFile(new File(Environment.getDataSystemDirectory(), fileName));
+ readState();
+ }
+
+ private void readState() {
+ AtomicFile file = mAtomicFile;
+ if (!file.exists()) {
+ AtomicFile fileToMigrateFrom =
+ new AtomicFile(new File(Environment.getDataSystemDirectory(),
+ "sensor_privacy.xml"));
+
+ if (fileToMigrateFrom.exists()) {
+ // Sample the start tag to determine if migration is needed
+ try (FileInputStream inputStream = fileToMigrateFrom.openRead()) {
+ TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
+ XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
+ file = fileToMigrateFrom;
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Caught an exception reading the state from storage: ", e);
+ // Delete the file to prevent the same error on subsequent calls and assume
+ // sensor privacy is not enabled.
+ fileToMigrateFrom.delete();
+ } catch (XmlPullParserException e) {
+ // No migration needed
+ }
+ }
+ }
+
+ Object nonupgradedState = null;
+ if (file.exists()) {
+ try (FileInputStream inputStream = file.openRead()) {
+ TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
+ XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
+ final int persistenceVersion = parser.getAttributeInt(null,
+ XML_ATTRIBUTE_PERSISTENCE_VERSION, 0);
+
+ // Use inline string literals for xml tags/attrs when parsing old versions since
+ // these should never be changed even with refactorings.
+ if (persistenceVersion == 0) {
+ int version = 0;
+ PVersion0 version0 = new PVersion0(version);
+ nonupgradedState = version0;
+ readPVersion0(parser, version0);
+ } else if (persistenceVersion == 1) {
+ int version = parser.getAttributeInt(null,
+ "version", 1);
+ PVersion1 version1 = new PVersion1(version);
+ nonupgradedState = version1;
+
+ readPVersion1(parser, version1);
+ } else if (persistenceVersion == CURRENT_PERSISTENCE_VERSION) {
+ int version = parser.getAttributeInt(null,
+ XML_ATTRIBUTE_VERSION, 2);
+ PVersion2 version2 = new PVersion2(version);
+ nonupgradedState = version2;
+
+ readPVersion2(parser, version2);
+ } else {
+ Log.e(LOG_TAG, "Unknown persistence version: " + persistenceVersion
+ + ". Deleting.",
+ new RuntimeException());
+ file.delete();
+ nonupgradedState = null;
+ }
+
+ } catch (IOException | XmlPullParserException | RuntimeException e) {
+ Log.e(LOG_TAG, "Caught an exception reading the state from storage: ", e);
+ // Delete the file to prevent the same error on subsequent calls and assume
+ // sensor privacy is not enabled.
+ file.delete();
+ nonupgradedState = null;
+ }
+ }
+
+ if (nonupgradedState == null) {
+ // New file, default state for current version goes here.
+ nonupgradedState = new PVersion2(2);
+ }
+
+ if (nonupgradedState instanceof PVersion0) {
+ nonupgradedState = PVersion1.fromPVersion0((PVersion0) nonupgradedState);
+ }
+ if (nonupgradedState instanceof PVersion1) {
+ nonupgradedState = PVersion2.fromPVersion1((PVersion1) nonupgradedState);
+ }
+ if (nonupgradedState instanceof PVersion2) {
+ PVersion2 upgradedState = (PVersion2) nonupgradedState;
+ mStates = upgradedState.mStates;
+ } else {
+ Log.e(LOG_TAG, "State not successfully upgraded.");
+ mStates = new ArrayMap<>();
+ }
+ }
+
+ private static void readPVersion0(TypedXmlPullParser parser, PVersion0 version0)
+ throws XmlPullParserException, IOException {
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ if ("individual-sensor-privacy".equals(parser.getName())) {
+ int sensor = XmlUtils.readIntAttribute(parser, "sensor");
+ boolean indEnabled = XmlUtils.readBooleanAttribute(parser,
+ "enabled");
+ version0.addState(sensor, indEnabled);
+ XmlUtils.skipCurrentTag(parser);
+ } else {
+ XmlUtils.nextElement(parser);
+ }
+ }
+ }
+
+ private static void readPVersion1(TypedXmlPullParser parser, PVersion1 version1)
+ throws XmlPullParserException, IOException {
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ XmlUtils.nextElement(parser);
+
+ if ("user".equals(parser.getName())) {
+ int currentUserId = parser.getAttributeInt(null, "id");
+ int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if ("individual-sensor-privacy".equals(parser.getName())) {
+ int sensor = parser.getAttributeInt(null, "sensor");
+ boolean isEnabled = parser.getAttributeBoolean(null,
+ "enabled");
+ version1.addState(currentUserId, sensor, isEnabled);
+ }
+ }
+ }
+ }
+ }
+
+ private static void readPVersion2(TypedXmlPullParser parser, PVersion2 version2)
+ throws XmlPullParserException, IOException {
+
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ XmlUtils.nextElement(parser);
+
+ if (XML_TAG_SENSOR_STATE.equals(parser.getName())) {
+ int toggleType = parser.getAttributeInt(null, XML_ATTRIBUTE_TOGGLE_TYPE);
+ int userId = parser.getAttributeInt(null, XML_ATTRIBUTE_USER_ID);
+ int sensor = parser.getAttributeInt(null, XML_ATTRIBUTE_SENSOR);
+ int state = parser.getAttributeInt(null, XML_ATTRIBUTE_STATE_TYPE);
+ long lastChange = parser.getAttributeLong(null, XML_ATTRIBUTE_LAST_CHANGE);
+
+ version2.addState(toggleType, userId, sensor, state, lastChange);
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ public SensorState getState(int toggleType, int userId, int sensor) {
+ return mStates.get(new TypeUserSensor(toggleType, userId, sensor));
+ }
+
+ public SensorState setState(int toggleType, int userId, int sensor, SensorState sensorState) {
+ return mStates.put(new TypeUserSensor(toggleType, userId, sensor), sensorState);
+ }
+
+ private static class TypeUserSensor {
+
+ int mType;
+ int mUserId;
+ int mSensor;
+
+ TypeUserSensor(int type, int userId, int sensor) {
+ mType = type;
+ mUserId = userId;
+ mSensor = sensor;
+ }
+
+ TypeUserSensor(TypeUserSensor typeUserSensor) {
+ this(typeUserSensor.mType, typeUserSensor.mUserId, typeUserSensor.mSensor);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TypeUserSensor)) return false;
+ TypeUserSensor that = (TypeUserSensor) o;
+ return mType == that.mType && mUserId == that.mUserId && mSensor == that.mSensor;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * (31 * mType + mUserId) + mSensor;
+ }
+ }
+
+ void schedulePersist() {
+ int numStates = mStates.size();
+
+ ArrayMap<TypeUserSensor, SensorState> statesCopy = new ArrayMap<>();
+ for (int i = 0; i < numStates; i++) {
+ statesCopy.put(new TypeUserSensor(mStates.keyAt(i)),
+ new SensorState(mStates.valueAt(i)));
+ }
+ IoThread.getHandler().sendMessage(
+ PooledLambda.obtainMessage(PersistedState::persist, this, statesCopy));
+ }
+
+ private void persist(ArrayMap<TypeUserSensor, SensorState> states) {
+ FileOutputStream outputStream = null;
+ try {
+ outputStream = mAtomicFile.startWrite();
+ TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
+ serializer.attributeInt(null, XML_ATTRIBUTE_PERSISTENCE_VERSION,
+ CURRENT_PERSISTENCE_VERSION);
+ serializer.attributeInt(null, XML_ATTRIBUTE_VERSION, CURRENT_VERSION);
+ for (int i = 0; i < states.size(); i++) {
+ TypeUserSensor userSensor = states.keyAt(i);
+ SensorState sensorState = states.valueAt(i);
+
+ serializer.startTag(null, XML_TAG_SENSOR_STATE);
+ serializer.attributeInt(null, XML_ATTRIBUTE_TOGGLE_TYPE,
+ userSensor.mType);
+ serializer.attributeInt(null, XML_ATTRIBUTE_USER_ID,
+ userSensor.mUserId);
+ serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR,
+ userSensor.mSensor);
+ serializer.attributeInt(null, XML_ATTRIBUTE_STATE_TYPE,
+ sensorState.getState());
+ serializer.attributeLong(null, XML_ATTRIBUTE_LAST_CHANGE,
+ sensorState.getLastChange());
+ serializer.endTag(null, XML_TAG_SENSOR_STATE);
+ }
+
+ serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
+ serializer.endDocument();
+ mAtomicFile.finishWrite(outputStream);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Caught an exception persisting the sensor privacy state: ", e);
+ mAtomicFile.failWrite(outputStream);
+ }
+ }
+
+ void dump(DualDumpOutputStream dumpStream) {
+ // Collect per user, then per sensor. <toggle type, state>
+ SparseArray<SparseArray<Pair<Integer, SensorState>>> statesMatrix = new SparseArray<>();
+ int numStates = mStates.size();
+ for (int i = 0; i < numStates; i++) {
+ int toggleType = mStates.keyAt(i).mType;
+ int userId = mStates.keyAt(i).mUserId;
+ int sensor = mStates.keyAt(i).mSensor;
+
+ SparseArray<Pair<Integer, SensorState>> userStates = statesMatrix.get(userId);
+ if (userStates == null) {
+ userStates = new SparseArray<>();
+ statesMatrix.put(userId, userStates);
+ }
+ userStates.put(sensor, new Pair<>(toggleType, mStates.valueAt(i)));
+ }
+
+ dumpStream.write("storage_implementation",
+ SensorPrivacyServiceDumpProto.STORAGE_IMPLEMENTATION,
+ SensorPrivacyStateControllerImpl.class.getName());
+
+ int numUsers = statesMatrix.size();
+ for (int i = 0; i < numUsers; i++) {
+ int userId = statesMatrix.keyAt(i);
+ long userToken = dumpStream.start("users", SensorPrivacyServiceDumpProto.USER);
+ dumpStream.write("user_id", SensorPrivacyUserProto.USER_ID, userId);
+ SparseArray<Pair<Integer, SensorState>> userStates = statesMatrix.valueAt(i);
+ int numSensors = userStates.size();
+ for (int j = 0; j < numSensors; j++) {
+ int sensor = userStates.keyAt(j);
+ int toggleType = userStates.valueAt(j).first;
+ SensorState sensorState = userStates.valueAt(j).second;
+ long sensorToken = dumpStream.start("sensors", SensorPrivacyUserProto.SENSORS);
+ dumpStream.write("sensor", SensorPrivacySensorProto.SENSOR, sensor);
+ long toggleToken = dumpStream.start("toggles", SensorPrivacySensorProto.TOGGLES);
+ dumpStream.write("toggle_type",
+ SensorPrivacyIndividualEnabledSensorProto.TOGGLE_TYPE,
+ toggleType);
+ dumpStream.write("state_type",
+ SensorPrivacyIndividualEnabledSensorProto.STATE_TYPE,
+ sensorState.getState());
+ dumpStream.write("last_change",
+ SensorPrivacyIndividualEnabledSensorProto.LAST_CHANGE,
+ sensorState.getLastChange());
+ dumpStream.end(toggleToken);
+ dumpStream.end(sensorToken);
+ }
+ dumpStream.end(userToken);
+ }
+ }
+
+ void forEachKnownState(QuadConsumer<Integer, Integer, Integer, SensorState> consumer) {
+ int numStates = mStates.size();
+ for (int i = 0; i < numStates; i++) {
+ TypeUserSensor tus = mStates.keyAt(i);
+ SensorState sensorState = mStates.valueAt(i);
+ consumer.accept(tus.mType, tus.mUserId, tus.mSensor, sensorState);
+ }
+ }
+
+ // Structure for persistence version 0
+ private static class PVersion0 {
+ private SparseArray<SensorState> mIndividualEnabled = new SparseArray<>();
+
+ private PVersion0(int version) {
+ if (version != 0) {
+ throw new RuntimeException("Only version 0 supported");
+ }
+ }
+
+ private void addState(int sensor, boolean enabled) {
+ mIndividualEnabled.put(sensor, new SensorState(enabled));
+ }
+
+ private void upgrade() {
+ // No op, only version 0 is supported
+ }
+ }
+
+ // Structure for persistence version 1
+ private static class PVersion1 {
+ private SparseArray<SparseArray<SensorState>> mIndividualEnabled = new SparseArray<>();
+
+ private PVersion1(int version) {
+ if (version != 1) {
+ throw new RuntimeException("Only version 1 supported");
+ }
+ }
+
+ private static PVersion1 fromPVersion0(PVersion0 version0) {
+ version0.upgrade();
+
+ PVersion1 result = new PVersion1(1);
+
+ int[] users = {UserHandle.USER_SYSTEM};
+ try {
+ users = LocalServices.getService(UserManagerInternal.class).getUserIds();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Unable to get users.", e);
+ }
+
+ // Copy global state to each user
+ for (int i = 0; i < users.length; i++) {
+ int userId = users[i];
+
+ for (int j = 0; j < version0.mIndividualEnabled.size(); j++) {
+ final int sensor = version0.mIndividualEnabled.keyAt(j);
+ final SensorState sensorState = version0.mIndividualEnabled.valueAt(j);
+
+ result.addState(userId, sensor, sensorState.isEnabled());
+ }
+ }
+
+ return result;
+ }
+
+ private void addState(int userId, int sensor, boolean enabled) {
+ SparseArray<SensorState> userIndividualSensorEnabled =
+ mIndividualEnabled.get(userId, new SparseArray<>());
+ mIndividualEnabled.put(userId, userIndividualSensorEnabled);
+
+ userIndividualSensorEnabled
+ .put(sensor, new SensorState(enabled));
+ }
+
+ private void upgrade() {
+ // No op, only version 1 is supported
+ }
+ }
+
+ // Structure for persistence version 2
+ private static class PVersion2 {
+ private ArrayMap<TypeUserSensor, SensorState> mStates = new ArrayMap<>();
+
+ private PVersion2(int version) {
+ if (version != 2) {
+ throw new RuntimeException("Only version 2 supported");
+ }
+ }
+
+ private static PVersion2 fromPVersion1(PVersion1 version1) {
+ version1.upgrade();
+
+ PVersion2 result = new PVersion2(2);
+
+ SparseArray<SparseArray<SensorState>> individualEnabled =
+ version1.mIndividualEnabled;
+ int numUsers = individualEnabled.size();
+ for (int i = 0; i < numUsers; i++) {
+ int userId = individualEnabled.keyAt(i);
+ SparseArray<SensorState> userIndividualEnabled = individualEnabled.valueAt(i);
+ int numSensors = userIndividualEnabled.size();
+ for (int j = 0; j < numSensors; j++) {
+ int sensor = userIndividualEnabled.keyAt(j);
+ SensorState sensorState = userIndividualEnabled.valueAt(j);
+ result.addState(SensorPrivacyManager.ToggleTypes.SOFTWARE,
+ userId, sensor, sensorState.getState(), sensorState.getLastChange());
+ }
+ }
+
+ return result;
+ }
+
+ private void addState(int toggleType, int userId, int sensor, int state,
+ long lastChange) {
+ mStates.put(new TypeUserSensor(toggleType, userId, sensor),
+ new SensorState(state, lastChange));
+ }
+ }
+
+ public void resetForTesting() {
+ mStates = new ArrayMap<>();
+ }
+}
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
similarity index 71%
rename from services/core/java/com/android/server/SensorPrivacyService.java
rename to services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 7a4d11c..e9b5f11 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.sensorprivacy;
import static android.Manifest.permission.MANAGE_SENSOR_PRIVACY;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
@@ -40,10 +40,10 @@
import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
import static android.hardware.SensorPrivacyManager.Sources.SHELL;
import static android.os.UserHandle.USER_NULL;
-import static android.os.UserHandle.USER_SYSTEM;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION;
+import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA;
@@ -83,7 +83,6 @@
import android.hardware.SensorPrivacyManagerInternal;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -97,26 +96,19 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.service.SensorPrivacyIndividualEnabledSensorProto;
-import android.service.SensorPrivacyServiceDumpProto;
-import android.service.SensorPrivacyUserProto;
import android.service.voice.VoiceInteractionManagerInternal;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
import android.text.Html;
+import android.text.Spanned;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.TypedXmlPullParser;
-import android.util.TypedXmlSerializer;
-import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
@@ -125,19 +117,14 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FunctionalUtils;
-import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -151,32 +138,10 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_LOGGING = false;
- /** Version number indicating compatibility parsing the persisted file */
- private static final int CURRENT_PERSISTENCE_VERSION = 1;
- /** Version number indicating the persisted data needs upgraded to match new internal data
- * structures and features */
- private static final int CURRENT_VERSION = 1;
-
- private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml";
- private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy";
- private static final String XML_TAG_USER = "user";
- private static final String XML_TAG_INDIVIDUAL_SENSOR_PRIVACY = "individual-sensor-privacy";
- private static final String XML_ATTRIBUTE_ID = "id";
- private static final String XML_ATTRIBUTE_PERSISTENCE_VERSION = "persistence-version";
- private static final String XML_ATTRIBUTE_VERSION = "version";
- private static final String XML_ATTRIBUTE_ENABLED = "enabled";
- private static final String XML_ATTRIBUTE_LAST_CHANGE = "last-change";
- private static final String XML_ATTRIBUTE_SENSOR = "sensor";
-
private static final String SENSOR_PRIVACY_CHANNEL_ID = Context.SENSOR_PRIVACY_SERVICE;
private static final String ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY =
SensorPrivacyService.class.getName() + ".action.disable_sensor_privacy";
- // These are associated with fields that existed for older persisted versions of files
- private static final int VER0_ENABLED = 0;
- private static final int VER0_INDIVIDUAL_ENABLED = 1;
- private static final int VER1_ENABLED = 0;
- private static final int VER1_INDIVIDUAL_ENABLED = 1;
public static final int REMINDER_DIALOG_DELAY_MILLIS = 500;
private final Context mContext;
@@ -200,6 +165,7 @@
public SensorPrivacyService(Context context) {
super(context);
+
mContext = context;
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mAppOpsManagerInternal = getLocalService(AppOpsManagerInternal.class);
@@ -247,12 +213,8 @@
private final SensorPrivacyHandler mHandler;
private final Object mLock = new Object();
- @GuardedBy("mLock")
- private final AtomicFile mAtomicFile;
- @GuardedBy("mLock")
- private SparseBooleanArray mEnabled = new SparseBooleanArray();
- @GuardedBy("mLock")
- private SparseArray<SparseArray<SensorState>> mIndividualEnabled = new SparseArray<>();
+
+ private SensorPrivacyStateController mSensorPrivacyStateController;
/**
* Packages for which not to show sensor use reminders.
@@ -266,34 +228,6 @@
private final ArrayMap<SensorUseReminderDialogInfo, ArraySet<Integer>>
mQueuedSensorUseReminderDialogs = new ArrayMap<>();
- private class SensorState {
- private boolean mEnabled;
- private long mLastChange;
-
- SensorState(boolean enabled) {
- mEnabled = enabled;
- mLastChange = getCurrentTimeMillis();
- }
-
- SensorState(boolean enabled, long lastChange) {
- mEnabled = enabled;
- if (lastChange < 0) {
- mLastChange = getCurrentTimeMillis();
- } else {
- mLastChange = lastChange;
- }
- }
-
- boolean setEnabled(boolean enabled) {
- if (mEnabled != enabled) {
- mEnabled = enabled;
- mLastChange = getCurrentTimeMillis();
- return true;
- }
- return false;
- }
- }
-
private class SensorUseReminderDialogInfo {
private int mTaskId;
private UserHandle mUser;
@@ -323,14 +257,7 @@
SensorPrivacyServiceImpl() {
mHandler = new SensorPrivacyHandler(FgThread.get().getLooper(), mContext);
- File sensorPrivacyFile = new File(Environment.getDataSystemDirectory(),
- SENSOR_PRIVACY_XML_FILE);
- mAtomicFile = new AtomicFile(sensorPrivacyFile);
- synchronized (mLock) {
- if (readPersistedSensorPrivacyStateLocked()) {
- persistSensorPrivacyStateLocked();
- }
- }
+ mSensorPrivacyStateController = SensorPrivacyStateController.getInstance();
int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE,
OP_CAMERA, OP_PHONE_CALL_CAMERA};
@@ -349,7 +276,26 @@
}
}, new IntentFilter(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY),
MANAGE_SENSOR_PRIVACY, null, Context.RECEIVER_EXPORTED);
+
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mSensorPrivacyStateController.forEachState(
+ (toggleType, userId, sensor, state) ->
+ logSensorPrivacyToggle(OTHER, sensor, state.isEnabled(),
+ state.getLastChange(), true)
+ );
+ }
+ }, new IntentFilter(Intent.ACTION_SHUTDOWN));
+
mUserManagerInternal.addUserRestrictionsListener(this);
+
+ mSensorPrivacyStateController.setAllSensorPrivacyListener(
+ mHandler, mHandler::handleSensorPrivacyChanged);
+ mSensorPrivacyStateController.setSensorPrivacyListener(
+ mHandler,
+ (toggleType, userId, sensor, state) -> mHandler.handleSensorPrivacyChanged(
+ userId, sensor, state.isEnabled()));
}
@Override
@@ -490,8 +436,9 @@
}
}
- String inputMethodComponent = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD);
+ String inputMethodComponent = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD,
+ mCurrentUser);
String inputMethodPackageName = null;
if (inputMethodComponent != null) {
inputMethodPackageName = ComponentName.unflattenFromString(
@@ -546,7 +493,8 @@
private void enqueueSensorUseReminderDialogAsync(int taskId, @NonNull UserHandle user,
@NonNull String packageName, int sensor) {
mHandler.sendMessage(PooledLambda.obtainMessage(
- this:: enqueueSensorUseReminderDialog, taskId, user, packageName, sensor));
+ SensorPrivacyServiceImpl::enqueueSensorUseReminderDialog, this, taskId, user,
+ packageName, sensor));
}
private void enqueueSensorUseReminderDialog(int taskId, @NonNull UserHandle user,
@@ -564,8 +512,8 @@
sensors.add(sensor);
}
mQueuedSensorUseReminderDialogs.put(info, sensors);
- mHandler.sendMessageDelayed(
- PooledLambda.obtainMessage(this::showSensorUserReminderDialog, info),
+ mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
+ SensorPrivacyServiceImpl::showSensorUserReminderDialog, this, info),
REMINDER_DIALOG_DELAY_MILLIS);
return;
}
@@ -655,28 +603,32 @@
notificationManager.createNotificationChannel(channel);
Icon icon = Icon.createWithResource(getUiContext().getResources(), iconRes);
+
+ String contentTitle = getUiContext().getString(messageRes);
+ Spanned contentText = Html.fromHtml(getUiContext().getString(
+ R.string.sensor_privacy_start_use_notification_content_text, packageLabel), 0);
+ PendingIntent contentIntent = PendingIntent.getActivity(mContext, sensor,
+ new Intent(Settings.ACTION_PRIVACY_SETTINGS),
+ PendingIntent.FLAG_IMMUTABLE
+ | PendingIntent.FLAG_UPDATE_CURRENT);
+
+ String actionTitle = getUiContext().getString(
+ R.string.sensor_privacy_start_use_dialog_turn_on_button);
+ PendingIntent actionIntent = PendingIntent.getBroadcast(mContext, sensor,
+ new Intent(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY)
+ .setPackage(mContext.getPackageName())
+ .putExtra(EXTRA_SENSOR, sensor)
+ .putExtra(Intent.EXTRA_USER, user),
+ PendingIntent.FLAG_IMMUTABLE
+ | PendingIntent.FLAG_UPDATE_CURRENT);
notificationManager.notify(notificationId,
new Notification.Builder(mContext, SENSOR_PRIVACY_CHANNEL_ID)
- .setContentTitle(getUiContext().getString(messageRes))
- .setContentText(Html.fromHtml(getUiContext().getString(
- R.string.sensor_privacy_start_use_notification_content_text,
- packageLabel),0))
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
.setSmallIcon(icon)
.addAction(new Notification.Action.Builder(icon,
- getUiContext().getString(
- R.string.sensor_privacy_start_use_dialog_turn_on_button),
- PendingIntent.getBroadcast(mContext, sensor,
- new Intent(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY)
- .setPackage(mContext.getPackageName())
- .putExtra(EXTRA_SENSOR, sensor)
- .putExtra(Intent.EXTRA_USER, user),
- PendingIntent.FLAG_IMMUTABLE
- | PendingIntent.FLAG_UPDATE_CURRENT))
- .build())
- .setContentIntent(PendingIntent.getActivity(mContext, sensor,
- new Intent(Settings.ACTION_PRIVACY_SETTINGS),
- PendingIntent.FLAG_IMMUTABLE
- | PendingIntent.FLAG_UPDATE_CURRENT))
+ actionTitle, actionIntent).build())
+ .setContentIntent(contentIntent)
.extend(new Notification.TvExtender())
.setTimeoutAfter(isTelevision(mContext)
? /* dismiss immediately */ 1
@@ -697,16 +649,7 @@
@Override
public void setSensorPrivacy(boolean enable) {
enforceManageSensorPrivacyPermission();
- // Keep the state consistent between all users to make it a single global state
- forAllUsers(userId -> setSensorPrivacy(userId, enable));
- }
-
- private void setSensorPrivacy(@UserIdInt int userId, boolean enable) {
- synchronized (mLock) {
- mEnabled.put(userId, enable);
- persistSensorPrivacyStateLocked();
- }
- mHandler.onSensorPrivacyChanged(enable);
+ mSensorPrivacyStateController.setAllSensorState(enable);
}
@Override
@@ -735,43 +678,23 @@
private void setIndividualSensorPrivacyUnchecked(int userId, int source, int sensor,
boolean enable) {
- synchronized (mLock) {
- SparseArray<SensorState> userIndividualEnabled = mIndividualEnabled.get(userId,
- new SparseArray<>());
- SensorState sensorState = userIndividualEnabled.get(sensor);
- long lastChange;
- if (sensorState != null) {
- lastChange = sensorState.mLastChange;
- if (!sensorState.setEnabled(enable)) {
- // State not changing
- return;
- }
- } else {
- sensorState = new SensorState(enable);
- lastChange = sensorState.mLastChange;
- userIndividualEnabled.put(sensor, sensorState);
- }
- mIndividualEnabled.put(userId, userIndividualEnabled);
-
- if (userId == mUserManagerInternal.getProfileParentId(userId)) {
- logSensorPrivacyToggle(source, sensor, sensorState.mEnabled, lastChange);
- }
-
- if (!enable) {
- final long token = Binder.clearCallingIdentity();
- try {
- // Remove any notifications prompting the user to disable sensory privacy
- NotificationManager notificationManager =
- mContext.getSystemService(NotificationManager.class);
-
- notificationManager.cancel(sensor);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- persistSensorPrivacyState();
- }
- mHandler.onSensorPrivacyChanged(userId, sensor, enable);
+ final long[] lastChange = new long[1];
+ mSensorPrivacyStateController.atomic(() -> {
+ SensorState sensorState = mSensorPrivacyStateController
+ .getState(SensorPrivacyManager.ToggleTypes.SOFTWARE, userId, sensor);
+ lastChange[0] = sensorState.getLastChange();
+ mSensorPrivacyStateController.setState(
+ SensorPrivacyManager.ToggleTypes.SOFTWARE, userId, sensor, enable, mHandler,
+ changeSuccessful -> {
+ if (changeSuccessful) {
+ if (userId == mUserManagerInternal.getProfileParentId(userId)) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ SensorPrivacyServiceImpl::logSensorPrivacyToggle, this,
+ source, sensor, enable, lastChange[0], false));
+ }
+ }
+ });
+ });
}
private boolean canChangeIndividualSensorPrivacy(@UserIdInt int userId, int sensor) {
@@ -801,14 +724,23 @@
}
private void logSensorPrivacyToggle(int source, int sensor, boolean enabled,
- long lastChange) {
+ long lastChange, boolean onShutDown) {
long logMins = Math.max(0, (getCurrentTimeMillis() - lastChange) / (1000 * 60));
int logAction = -1;
- if (enabled) {
- logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
+ if (onShutDown) {
+ // TODO ACTION_POWER_OFF_WHILE_(ON/OFF)
+ if (enabled) {
+ logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
+ } else {
+ logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
+ }
} else {
- logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
+ if (enabled) {
+ logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
+ } else {
+ logAction = PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
+ }
}
int logSensor = -1;
@@ -895,13 +827,7 @@
@Override
public boolean isSensorPrivacyEnabled() {
enforceObserveSensorPrivacyPermission();
- return isSensorPrivacyEnabled(USER_SYSTEM);
- }
-
- private boolean isSensorPrivacyEnabled(@UserIdInt int userId) {
- synchronized (mLock) {
- return mEnabled.get(userId, false);
- }
+ return mSensorPrivacyStateController.getAllSensorState();
}
@Override
@@ -918,239 +844,8 @@
if (userId == UserHandle.USER_CURRENT) {
userId = mCurrentUser;
}
- synchronized (mLock) {
- return isIndividualSensorPrivacyEnabledLocked(userId, sensor);
- }
- }
-
- private boolean isIndividualSensorPrivacyEnabledLocked(int userId, int sensor) {
- SparseArray<SensorState> states = mIndividualEnabled.get(userId);
- if (states == null) {
- return false;
- }
- SensorState state = states.get(sensor);
- if (state == null) {
- return false;
- }
- return state.mEnabled;
- }
-
- /**
- * Returns the state of sensor privacy from persistent storage.
- */
- private boolean readPersistedSensorPrivacyStateLocked() {
- // if the file does not exist then sensor privacy has not yet been enabled on
- // the device.
-
- SparseArray<Object> map = new SparseArray<>();
- int version = -1;
-
- if (mAtomicFile.exists()) {
- try (FileInputStream inputStream = mAtomicFile.openRead()) {
- TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
- XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
- final int persistenceVersion = parser.getAttributeInt(null,
- XML_ATTRIBUTE_PERSISTENCE_VERSION, 0);
-
- // Use inline string literals for xml tags/attrs when parsing old versions since
- // these should never be changed even with refactorings.
- if (persistenceVersion == 0) {
- boolean enabled = parser.getAttributeBoolean(null, "enabled", false);
- SparseArray<SensorState> individualEnabled = new SparseArray<>();
- version = 0;
-
- XmlUtils.nextElement(parser);
- while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if ("individual-sensor-privacy".equals(tagName)) {
- int sensor = XmlUtils.readIntAttribute(parser, "sensor");
- boolean indEnabled = XmlUtils.readBooleanAttribute(parser,
- "enabled");
- individualEnabled.put(sensor, new SensorState(indEnabled));
- XmlUtils.skipCurrentTag(parser);
- } else {
- XmlUtils.nextElement(parser);
- }
- }
- map.put(VER0_ENABLED, enabled);
- map.put(VER0_INDIVIDUAL_ENABLED, individualEnabled);
- } else if (persistenceVersion == CURRENT_PERSISTENCE_VERSION) {
- SparseBooleanArray enabled = new SparseBooleanArray();
- SparseArray<SparseArray<SensorState>> individualEnabled =
- new SparseArray<>();
- version = parser.getAttributeInt(null,
- XML_ATTRIBUTE_VERSION, 1);
-
- int currentUserId = -1;
- while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
- XmlUtils.nextElement(parser);
- String tagName = parser.getName();
- if (XML_TAG_USER.equals(tagName)) {
- currentUserId = parser.getAttributeInt(null, XML_ATTRIBUTE_ID);
- boolean isEnabled = parser.getAttributeBoolean(null,
- XML_ATTRIBUTE_ENABLED);
- if (enabled.indexOfKey(currentUserId) >= 0) {
- Log.e(TAG, "User listed multiple times in file.",
- new RuntimeException());
- mAtomicFile.delete();
- version = -1;
- break;
- }
-
- if (mUserManagerInternal.getUserInfo(currentUserId) == null) {
- // User may no longer exist, skip this user
- currentUserId = -1;
- continue;
- }
-
- enabled.put(currentUserId, isEnabled);
- }
- if (XML_TAG_INDIVIDUAL_SENSOR_PRIVACY.equals(tagName)) {
- if (mUserManagerInternal.getUserInfo(currentUserId) == null) {
- // User may no longer exist or isn't set
- continue;
- }
- int sensor = parser.getAttributeInt(null, XML_ATTRIBUTE_SENSOR);
- boolean isEnabled = parser.getAttributeBoolean(null,
- XML_ATTRIBUTE_ENABLED);
- long lastChange = parser
- .getAttributeLong(null, XML_ATTRIBUTE_LAST_CHANGE, -1);
- SparseArray<SensorState> userIndividualEnabled =
- individualEnabled.get(currentUserId, new SparseArray<>());
-
- userIndividualEnabled
- .put(sensor, new SensorState(isEnabled, lastChange));
- individualEnabled.put(currentUserId, userIndividualEnabled);
- }
- }
-
- map.put(VER1_ENABLED, enabled);
- map.put(VER1_INDIVIDUAL_ENABLED, individualEnabled);
- } else {
- Log.e(TAG, "Unknown persistence version: " + persistenceVersion
- + ". Deleting.",
- new RuntimeException());
- mAtomicFile.delete();
- version = -1;
- }
-
- } catch (IOException | XmlPullParserException e) {
- Log.e(TAG, "Caught an exception reading the state from storage: ", e);
- // Delete the file to prevent the same error on subsequent calls and assume
- // sensor privacy is not enabled.
- mAtomicFile.delete();
- version = -1;
- }
- }
-
- try {
- return upgradeAndInit(version, map);
- } catch (Exception e) {
- Log.wtf(TAG, "Failed to upgrade and set sensor privacy state,"
- + " resetting to default.", e);
- mEnabled = new SparseBooleanArray();
- mIndividualEnabled = new SparseArray<>();
- return true;
- }
- }
-
- private boolean upgradeAndInit(int version, SparseArray map) {
- if (version == -1) {
- // New file, default state for current version goes here.
- mEnabled = new SparseBooleanArray();
- mIndividualEnabled = new SparseArray<>();
- forAllUsers(userId -> mEnabled.put(userId, false));
- forAllUsers(userId -> mIndividualEnabled.put(userId, new SparseArray<>()));
- return true;
- }
- boolean upgraded = false;
- final int[] users = getLocalService(UserManagerInternal.class).getUserIds();
- if (version == 0) {
- final boolean enabled = (boolean) map.get(VER0_ENABLED);
- final SparseArray<SensorState> individualEnabled =
- (SparseArray<SensorState>) map.get(VER0_INDIVIDUAL_ENABLED);
-
- final SparseBooleanArray perUserEnabled = new SparseBooleanArray();
- final SparseArray<SparseArray<SensorState>> perUserIndividualEnabled =
- new SparseArray<>();
-
- // Copy global state to each user
- for (int i = 0; i < users.length; i++) {
- int user = users[i];
- perUserEnabled.put(user, enabled);
- SparseArray<SensorState> userIndividualSensorEnabled = new SparseArray<>();
- perUserIndividualEnabled.put(user, userIndividualSensorEnabled);
- for (int j = 0; j < individualEnabled.size(); j++) {
- final int sensor = individualEnabled.keyAt(j);
- final SensorState isSensorEnabled = individualEnabled.valueAt(j);
- userIndividualSensorEnabled.put(sensor, isSensorEnabled);
- }
- }
-
- map.clear();
- map.put(VER1_ENABLED, perUserEnabled);
- map.put(VER1_INDIVIDUAL_ENABLED, perUserIndividualEnabled);
-
- version = 1;
- upgraded = true;
- }
- if (version == CURRENT_VERSION) {
- mEnabled = (SparseBooleanArray) map.get(VER1_ENABLED);
- mIndividualEnabled =
- (SparseArray<SparseArray<SensorState>>) map.get(VER1_INDIVIDUAL_ENABLED);
- }
- return upgraded;
- }
-
- /**
- * Persists the state of sensor privacy.
- */
- private void persistSensorPrivacyState() {
- synchronized (mLock) {
- persistSensorPrivacyStateLocked();
- }
- }
-
- private void persistSensorPrivacyStateLocked() {
- FileOutputStream outputStream = null;
- try {
- outputStream = mAtomicFile.startWrite();
- TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream);
- serializer.startDocument(null, true);
- serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
- serializer.attributeInt(
- null, XML_ATTRIBUTE_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION);
- serializer.attributeInt(null, XML_ATTRIBUTE_VERSION, CURRENT_VERSION);
- forAllUsers(userId -> {
- serializer.startTag(null, XML_TAG_USER);
- serializer.attributeInt(null, XML_ATTRIBUTE_ID, userId);
- serializer.attributeBoolean(
- null, XML_ATTRIBUTE_ENABLED, isSensorPrivacyEnabled(userId));
-
- SparseArray<SensorState> individualEnabled =
- mIndividualEnabled.get(userId, new SparseArray<>());
- int numIndividual = individualEnabled.size();
- for (int i = 0; i < numIndividual; i++) {
- serializer.startTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY);
- int sensor = individualEnabled.keyAt(i);
- SensorState sensorState = individualEnabled.valueAt(i);
- boolean enabled = sensorState.mEnabled;
- long lastChange = sensorState.mLastChange;
- serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR, sensor);
- serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled);
- serializer.attributeLong(null, XML_ATTRIBUTE_LAST_CHANGE, lastChange);
- serializer.endTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY);
- }
- serializer.endTag(null, XML_TAG_USER);
-
- });
- serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
- serializer.endDocument();
- mAtomicFile.finishWrite(outputStream);
- } catch (IOException e) {
- Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e);
- mAtomicFile.failWrite(outputStream);
- }
+ return mSensorPrivacyStateController.getState(SensorPrivacyManager.ToggleTypes.SOFTWARE,
+ userId, sensor).isEnabled();
}
@Override
@@ -1285,23 +980,23 @@
}
private void userSwitching(int from, int to) {
- boolean micState;
- boolean camState;
- boolean prevMicState;
- boolean prevCamState;
- synchronized (mLock) {
- prevMicState = isIndividualSensorPrivacyEnabledLocked(from, MICROPHONE);
- prevCamState = isIndividualSensorPrivacyEnabledLocked(from, CAMERA);
- micState = isIndividualSensorPrivacyEnabledLocked(to, MICROPHONE);
- camState = isIndividualSensorPrivacyEnabledLocked(to, CAMERA);
+ final boolean[] micState = new boolean[1];
+ final boolean[] camState = new boolean[1];
+ final boolean[] prevMicState = new boolean[1];
+ final boolean[] prevCamState = new boolean[1];
+ mSensorPrivacyStateController.atomic(() -> {
+ prevMicState[0] = isIndividualSensorPrivacyEnabled(from, MICROPHONE);
+ prevCamState[0] = isIndividualSensorPrivacyEnabled(from, CAMERA);
+ micState[0] = isIndividualSensorPrivacyEnabled(to, MICROPHONE);
+ camState[0] = isIndividualSensorPrivacyEnabled(to, CAMERA);
+ });
+ if (from == USER_NULL || prevMicState[0] != micState[0]) {
+ mHandler.onUserGlobalSensorPrivacyChanged(MICROPHONE, micState[0]);
+ setGlobalRestriction(MICROPHONE, micState[0]);
}
- if (from == USER_NULL || prevMicState != micState) {
- mHandler.onUserGlobalSensorPrivacyChanged(MICROPHONE, micState);
- setGlobalRestriction(MICROPHONE, micState);
- }
- if (from == USER_NULL || prevCamState != camState) {
- mHandler.onUserGlobalSensorPrivacyChanged(CAMERA, camState);
- setGlobalRestriction(CAMERA, camState);
+ if (from == USER_NULL || prevCamState[0] != camState[0]) {
+ mHandler.onUserGlobalSensorPrivacyChanged(CAMERA, camState[0]);
+ setGlobalRestriction(CAMERA, camState[0]);
}
}
@@ -1395,12 +1090,14 @@
final long identity = Binder.clearCallingIdentity();
try {
if (dumpAsProto) {
- dump(new DualDumpOutputStream(new ProtoOutputStream(fd)));
+ mSensorPrivacyStateController.dump(
+ new DualDumpOutputStream(new ProtoOutputStream(fd)));
} else {
pw.println("SENSOR PRIVACY MANAGER STATE (dumpsys "
+ Context.SENSOR_PRIVACY_SERVICE + ")");
- dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")));
+ mSensorPrivacyStateController.dump(
+ new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")));
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1408,45 +1105,6 @@
}
/**
- * Dump state to {@link DualDumpOutputStream}.
- *
- * @param dumpStream The destination to dump to
- */
- private void dump(@NonNull DualDumpOutputStream dumpStream) {
- synchronized (mLock) {
-
- forAllUsers(userId -> {
- long userToken = dumpStream.start("users", SensorPrivacyServiceDumpProto.USER);
- dumpStream.write("user_id", SensorPrivacyUserProto.USER_ID, userId);
- dumpStream.write("is_enabled", SensorPrivacyUserProto.IS_ENABLED,
- mEnabled.get(userId, false));
-
- SparseArray<SensorState> individualEnabled = mIndividualEnabled.get(userId);
- if (individualEnabled != null) {
- int numIndividualEnabled = individualEnabled.size();
- for (int i = 0; i < numIndividualEnabled; i++) {
- long individualToken = dumpStream.start("individual_enabled_sensor",
- SensorPrivacyUserProto.INDIVIDUAL_ENABLED_SENSOR);
-
- dumpStream.write("sensor",
- SensorPrivacyIndividualEnabledSensorProto.SENSOR,
- individualEnabled.keyAt(i));
- dumpStream.write("is_enabled",
- SensorPrivacyIndividualEnabledSensorProto.IS_ENABLED,
- individualEnabled.valueAt(i).mEnabled);
- // TODO dump last change
-
- dumpStream.end(individualToken);
- }
- }
- dumpStream.end(userToken);
- });
- }
-
- dumpStream.flush();
- }
-
- /**
* Convert a string into a {@link SensorPrivacyManager.Sensors.Sensor id}.
*
* @param sensor The name to convert
@@ -1504,25 +1162,6 @@
setIndividualSensorPrivacy(userId, SHELL, sensor, false);
}
break;
- case "reset": {
- int sensor = sensorStrToId(getNextArgRequired());
- if (sensor == UNKNOWN) {
- pw.println("Invalid sensor");
- return -1;
- }
-
- enforceManageSensorPrivacyPermission();
-
- synchronized (mLock) {
- SparseArray<SensorState> individualEnabled =
- mIndividualEnabled.get(userId);
- if (individualEnabled != null) {
- individualEnabled.delete(sensor);
- }
- persistSensorPrivacyState();
- }
- }
- break;
default:
return handleDefaultCommands(cmd);
}
@@ -1545,9 +1184,6 @@
pw.println(" disable USER_ID SENSOR");
pw.println(" Disable privacy for a certain sensor.");
pw.println("");
- pw.println(" reset USER_ID SENSOR");
- pw.println(" Reset privacy state for a certain sensor.");
- pw.println("");
}
}).exec(this, in, out, err, args, callback, resultReceiver);
}
@@ -1581,22 +1217,6 @@
mContext = context;
}
- public void onSensorPrivacyChanged(boolean enabled) {
- sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged,
- this, enabled));
- sendMessage(
- PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState,
- mSensorPrivacyServiceImpl));
- }
-
- public void onSensorPrivacyChanged(int userId, int sensor, boolean enabled) {
- sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged,
- this, userId, sensor, enabled));
- sendMessage(
- PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState,
- mSensorPrivacyServiceImpl));
- }
-
public void onUserGlobalSensorPrivacyChanged(int sensor, boolean enabled) {
sendMessage(PooledLambda.obtainMessage(
SensorPrivacyHandler::handleUserGlobalSensorPrivacyChanged,
@@ -1692,6 +1312,7 @@
}
public void handleSensorPrivacyChanged(int userId, int sensor, boolean enabled) {
+ // TODO handle hardware
mSensorPrivacyManagerInternal.dispatch(userId, sensor, enabled);
SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
mIndividualSensorListeners.get(userId);
@@ -1972,11 +1593,7 @@
}
}
- private static long getCurrentTimeMillis() {
- try {
- return SystemClock.currentNetworkTimeMillis();
- } catch (Exception e) {
- return System.currentTimeMillis();
- }
+ static long getCurrentTimeMillis() {
+ return SystemClock.elapsedRealtime();
}
}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java
new file mode 100644
index 0000000..9694958
--- /dev/null
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java
@@ -0,0 +1,158 @@
+/*
+ * 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.sensorprivacy;
+
+import android.os.Handler;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+abstract class SensorPrivacyStateController {
+
+ private static SensorPrivacyStateController sInstance;
+
+ AllSensorStateController mAllSensorStateController = AllSensorStateController.getInstance();
+
+ private final Object mLock = new Object();
+
+ static SensorPrivacyStateController getInstance() {
+ if (sInstance == null) {
+ sInstance = SensorPrivacyStateControllerImpl.getInstance();
+ }
+
+ return sInstance;
+ }
+
+ SensorState getState(int toggleType, int userId, int sensor) {
+ synchronized (mLock) {
+ return getStateLocked(toggleType, userId, sensor);
+ }
+ }
+
+ void setState(int toggleType, int userId, int sensor, boolean enabled, Handler callbackHandler,
+ SetStateResultCallback callback) {
+ synchronized (mLock) {
+ setStateLocked(toggleType, userId, sensor, enabled, callbackHandler, callback);
+ }
+ }
+
+ void setSensorPrivacyListener(Handler handler,
+ SensorPrivacyListener listener) {
+ synchronized (mLock) {
+ setSensorPrivacyListenerLocked(handler, listener);
+ }
+ }
+
+ // Following calls are for the developer settings sensor mute feature
+ boolean getAllSensorState() {
+ synchronized (mLock) {
+ return mAllSensorStateController.getAllSensorStateLocked();
+ }
+ }
+
+ void setAllSensorState(boolean enable) {
+ synchronized (mLock) {
+ mAllSensorStateController.setAllSensorStateLocked(enable);
+ }
+ }
+
+ void setAllSensorPrivacyListener(Handler handler, AllSensorPrivacyListener listener) {
+ synchronized (mLock) {
+ mAllSensorStateController.setAllSensorPrivacyListenerLocked(handler, listener);
+ }
+ }
+
+ void persistAll() {
+ synchronized (mLock) {
+ mAllSensorStateController.schedulePersistLocked();
+ schedulePersistLocked();
+ }
+ }
+
+ void forEachState(SensorPrivacyStateConsumer consumer) {
+ synchronized (mLock) {
+ forEachStateLocked(consumer);
+ }
+ }
+
+ void dump(DualDumpOutputStream dumpStream) {
+ synchronized (mLock) {
+ mAllSensorStateController.dumpLocked(dumpStream);
+ dumpLocked(dumpStream);
+ }
+ dumpStream.flush();
+ }
+
+ public void atomic(Runnable r) {
+ synchronized (mLock) {
+ r.run();
+ }
+ }
+
+ interface SensorPrivacyListener {
+ void onSensorPrivacyChanged(int toggleType, int userId, int sensor, SensorState state);
+ }
+
+ interface SensorPrivacyStateConsumer {
+ void accept(int toggleType, int userId, int sensor, SensorState state);
+ }
+
+ interface SetStateResultCallback {
+ void callback(boolean changed);
+ }
+
+ interface AllSensorPrivacyListener {
+ void onAllSensorPrivacyChanged(boolean enabled);
+ }
+
+ @GuardedBy("mLock")
+ abstract SensorState getStateLocked(int toggleType, int userId, int sensor);
+
+ @GuardedBy("mLock")
+ abstract void setStateLocked(int toggleType, int userId, int sensor, boolean enabled,
+ Handler callbackHandler, SetStateResultCallback callback);
+
+ @GuardedBy("mLock")
+ abstract void setSensorPrivacyListenerLocked(Handler handler,
+ SensorPrivacyListener listener);
+
+ @GuardedBy("mLock")
+ abstract void schedulePersistLocked();
+
+ @GuardedBy("mLock")
+ abstract void forEachStateLocked(SensorPrivacyStateConsumer consumer);
+
+ @GuardedBy("mLock")
+ abstract void dumpLocked(DualDumpOutputStream dumpStream);
+
+ static void sendSetStateCallback(Handler callbackHandler,
+ SetStateResultCallback callback, boolean success) {
+ callbackHandler.sendMessage(PooledLambda.obtainMessage(SetStateResultCallback::callback,
+ callback, success));
+ }
+
+ /**
+ * Used for unit testing
+ */
+ void resetForTesting() {
+ mAllSensorStateController.resetForTesting();
+ resetForTestingImpl();
+ sInstance = null;
+ }
+ abstract void resetForTestingImpl();
+}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java
new file mode 100644
index 0000000..d1ea8e9
--- /dev/null
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java
@@ -0,0 +1,142 @@
+/*
+ * 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.sensorprivacy;
+
+import android.hardware.SensorPrivacyManager;
+import android.os.Handler;
+
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.util.Objects;
+
+class SensorPrivacyStateControllerImpl extends SensorPrivacyStateController {
+
+ private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy_impl.xml";
+
+ private static SensorPrivacyStateControllerImpl sInstance;
+
+ private PersistedState mPersistedState;
+
+ private SensorPrivacyListener mListener;
+ private Handler mListenerHandler;
+
+ static SensorPrivacyStateController getInstance() {
+ if (sInstance == null) {
+ sInstance = new SensorPrivacyStateControllerImpl();
+ }
+ return sInstance;
+ }
+
+ private SensorPrivacyStateControllerImpl() {
+ mPersistedState = PersistedState.fromFile(SENSOR_PRIVACY_XML_FILE);
+ persistAll();
+ }
+
+ @Override
+ SensorState getStateLocked(int toggleType, int userId, int sensor) {
+ if (toggleType == SensorPrivacyManager.ToggleTypes.HARDWARE) {
+ // Device doesn't support hardware state
+ return getDefaultSensorState();
+ }
+ SensorState sensorState = mPersistedState.getState(toggleType, userId, sensor);
+ if (sensorState != null) {
+ return new SensorState(sensorState);
+ }
+ return getDefaultSensorState();
+ }
+
+ private static SensorState getDefaultSensorState() {
+ return new SensorState(false);
+ }
+
+ @Override
+ void setStateLocked(int toggleType, int userId, int sensor, boolean enabled,
+ Handler callbackHandler, SetStateResultCallback callback) {
+ if (toggleType != SensorPrivacyManager.ToggleTypes.SOFTWARE) {
+ // Implementation only supports software switch
+ callbackHandler.sendMessage(PooledLambda.obtainMessage(
+ SetStateResultCallback::callback, callback, false));
+ return;
+ }
+ // Changing the SensorState's mEnabled updates the timestamp of its last change.
+ // A nonexistent state -> unmuted should not set the timestamp.
+ SensorState lastState = mPersistedState.getState(toggleType, userId, sensor);
+ if (lastState == null) {
+ if (!enabled) {
+ sendSetStateCallback(callbackHandler, callback, false);
+ return;
+ } else if (enabled) {
+ SensorState sensorState = new SensorState(true);
+ mPersistedState.setState(toggleType, userId, sensor, sensorState);
+ notifyStateChangeLocked(toggleType, userId, sensor, sensorState);
+ sendSetStateCallback(callbackHandler, callback, true);
+ return;
+ }
+ }
+ if (lastState.setEnabled(enabled)) {
+ notifyStateChangeLocked(toggleType, userId, sensor, lastState);
+ sendSetStateCallback(callbackHandler, callback, true);
+ return;
+ }
+ sendSetStateCallback(callbackHandler, callback, false);
+ }
+
+ private void notifyStateChangeLocked(int toggleType, int userId, int sensor,
+ SensorState sensorState) {
+ if (mListenerHandler != null && mListener != null) {
+ mListenerHandler.sendMessage(PooledLambda.obtainMessage(
+ SensorPrivacyListener::onSensorPrivacyChanged, mListener,
+ toggleType, userId, sensor, new SensorState(sensorState)));
+ }
+ schedulePersistLocked();
+ }
+
+ @Override
+ void setSensorPrivacyListenerLocked(Handler handler, SensorPrivacyListener listener) {
+ Objects.requireNonNull(handler);
+ Objects.requireNonNull(listener);
+ if (mListener != null) {
+ throw new IllegalStateException("Listener is already set");
+ }
+ mListener = listener;
+ mListenerHandler = handler;
+ }
+
+ @Override
+ void schedulePersistLocked() {
+ mPersistedState.schedulePersist();
+ }
+
+ @Override
+ void forEachStateLocked(SensorPrivacyStateConsumer consumer) {
+ mPersistedState.forEachKnownState(consumer::accept);
+ }
+
+ @Override
+ void resetForTestingImpl() {
+ mPersistedState.resetForTesting();
+ mListener = null;
+ mListenerHandler = null;
+ sInstance = null;
+ }
+
+ @Override
+ void dumpLocked(DualDumpOutputStream dumpStream) {
+ mPersistedState.dump(dumpStream);
+ }
+}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorState.java b/services/core/java/com/android/server/sensorprivacy/SensorState.java
new file mode 100644
index 0000000..b92e2c8
--- /dev/null
+++ b/services/core/java/com/android/server/sensorprivacy/SensorState.java
@@ -0,0 +1,79 @@
+/*
+ * 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.sensorprivacy;
+
+import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED;
+import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED;
+
+import static com.android.server.sensorprivacy.SensorPrivacyService.getCurrentTimeMillis;
+
+class SensorState {
+
+ private int mStateType;
+ private long mLastChange;
+
+ SensorState(int stateType) {
+ mStateType = stateType;
+ mLastChange = getCurrentTimeMillis();
+ }
+
+ SensorState(int stateType, long lastChange) {
+ mStateType = stateType;
+ mLastChange = Math.min(getCurrentTimeMillis(), lastChange);
+ }
+
+ SensorState(SensorState sensorState) {
+ mStateType = sensorState.getState();
+ mLastChange = sensorState.getLastChange();
+ }
+
+ boolean setState(int stateType) {
+ if (mStateType != stateType) {
+ mStateType = stateType;
+ mLastChange = getCurrentTimeMillis();
+ return true;
+ }
+ return false;
+ }
+
+ int getState() {
+ return mStateType;
+ }
+
+ long getLastChange() {
+ return mLastChange;
+ }
+
+ // Below are some convenience members for when dealing with enabled/disabled
+
+ private static int enabledToState(boolean enabled) {
+ return enabled ? ENABLED : DISABLED;
+ }
+
+ SensorState(boolean enabled) {
+ this(enabledToState(enabled));
+ }
+
+ boolean setEnabled(boolean enabled) {
+ return setState(enabledToState(enabled));
+ }
+
+ boolean isEnabled() {
+ return getState() == ENABLED;
+ }
+
+}
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/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 5d879ce..3d7dead 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -906,8 +906,7 @@
proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
results, newIntents, r.takeOptions(), isTransitionForward,
proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
- r.createFixedRotationAdjustmentsIfNeeded(), r.shareableActivityToken,
- r.getLaunchedFromBubble()));
+ r.shareableActivityToken, r.getLaunchedFromBubble()));
// Set desired final state.
final ActivityLifecycleItem lifecycleItem;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a1c823e..4009220 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -170,7 +170,6 @@
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
-import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
@@ -1684,9 +1683,6 @@
// adjustments of previous rotated activity should be cleared earlier. Otherwise if
// the current top is in the same process, it may get the rotated state. The transform
// will be cleared later with transition callback to ensure smooth animation.
- if (hasTopFixedRotationLaunchingApp()) {
- mFixedRotationLaunchingApp.notifyFixedRotationTransform(false /* enabled */);
- }
return false;
}
if (!r.getDisplayArea().matchParentBounds()) {
@@ -2191,8 +2187,8 @@
outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale);
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw,
- dh);
+ outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw, dh);
+ outConfig.windowConfiguration.setDisplayRotation(rotation);
}
/**
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/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 6d8203c..db231f6 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
@@ -37,20 +36,15 @@
import android.annotation.CallSuper;
import android.annotation.Nullable;
-import android.app.servertransaction.FixedRotationAdjustmentsItem;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import android.view.DisplayAdjustments.FixedRotationAdjustments;
import android.view.DisplayInfo;
import android.view.InsetsState;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.view.WindowManager.LayoutParams.WindowType;
import android.window.WindowContext;
@@ -485,9 +479,6 @@
* This should only be called when {@link #mFixedRotationTransformState} is non-null.
*/
private void onFixedRotationStatePrepared() {
- // Send the adjustment info first so when the client receives configuration change, it can
- // get the rotated display metrics.
- notifyFixedRotationTransform(true /* enabled */);
// Resolve the rotated configuration.
onConfigurationChanged(getParent().getConfiguration());
final ActivityRecord r = asActivityRecord();
@@ -543,7 +534,6 @@
for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
final WindowToken token = state.mAssociatedTokens.get(i);
token.mFixedRotationTransformState = null;
- token.notifyFixedRotationTransform(false /* enabled */);
if (applyDisplayRotation == null) {
// Notify cancellation because the display does not change rotation.
token.cancelFixedRotationTransform();
@@ -551,44 +541,6 @@
}
}
- /** Notifies application side to enable or disable the rotation adjustment of display info. */
- void notifyFixedRotationTransform(boolean enabled) {
- FixedRotationAdjustments adjustments = null;
- // A token may contain windows of the same processes or different processes. The list is
- // used to avoid sending the same adjustments to a process multiple times.
- ArrayList<WindowProcessController> notifiedProcesses = null;
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState w = mChildren.get(i);
- final WindowProcessController app;
- if (w.mAttrs.type == TYPE_APPLICATION_STARTING) {
- // Use the host activity because starting window is controlled by window manager.
- final ActivityRecord r = asActivityRecord();
- if (r == null) {
- continue;
- }
- app = r.app;
- } else {
- app = mWmService.mAtmService.mProcessMap.getProcess(w.mSession.mPid);
- }
- if (app == null || !app.hasThread()) {
- continue;
- }
- if (notifiedProcesses == null) {
- notifiedProcesses = new ArrayList<>(2);
- adjustments = enabled ? createFixedRotationAdjustmentsIfNeeded() : null;
- } else if (notifiedProcesses.contains(app)) {
- continue;
- }
- notifiedProcesses.add(app);
- try {
- mWmService.mAtmService.getLifecycleManager().scheduleTransaction(
- app.getThread(), FixedRotationAdjustmentsItem.obtain(token, adjustments));
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to schedule DisplayAdjustmentsItem to " + app, e);
- }
- }
- }
-
/** Restores the changes that applies to this container. */
private void cancelFixedRotationTransform() {
final WindowContainer<?> parent = getParent();
@@ -609,15 +561,6 @@
void onCancelFixedRotationTransform(int originalDisplayRotation) {
}
- FixedRotationAdjustments createFixedRotationAdjustmentsIfNeeded() {
- if (!isFixedRotationTransforming()) {
- return null;
- }
- final DisplayInfo displayInfo = mFixedRotationTransformState.mDisplayInfo;
- return new FixedRotationAdjustments(displayInfo.rotation, displayInfo.appWidth,
- displayInfo.appHeight, displayInfo.displayCutout);
- }
-
@Override
void resolveOverrideConfiguration(Configuration newParentConfig) {
super.resolveOverrideConfiguration(newParentConfig);
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/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 574dbfd..79d8036 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -38,6 +38,10 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
+ <xs:element type="thermalThrottling" name="thermalThrottling">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
<xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
maxOccurs="1"/>
<xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" />
@@ -154,6 +158,37 @@
</xs:restriction>
</xs:simpleType>
+ <xs:complexType name="thermalThrottling">
+ <xs:complexType>
+ <xs:element type="brightnessThrottlingMap" name="brightnessThrottlingMap">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:complexType>
+ </xs:complexType>
+
+ <xs:complexType name="brightnessThrottlingMap">
+ <xs:sequence>
+ <xs:element name="brightnessThrottlingPoint" type="brightnessThrottlingPoint" maxOccurs="unbounded" minOccurs="1">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="brightnessThrottlingPoint">
+ <xs:sequence>
+ <xs:element type="thermalStatus" name="thermalStatus">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element type="nonNegativeDecimal" name="brightness">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
<xs:complexType name="nitsMap">
<xs:sequence>
<xs:element name="point" type="point" maxOccurs="unbounded" minOccurs="2">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 04f0916..0b7df4d 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -7,6 +7,19 @@
method public final void setMinimum(@NonNull java.math.BigDecimal);
}
+ public class BrightnessThrottlingMap {
+ ctor public BrightnessThrottlingMap();
+ method @NonNull public final java.util.List<com.android.server.display.config.BrightnessThrottlingPoint> getBrightnessThrottlingPoint();
+ }
+
+ public class BrightnessThrottlingPoint {
+ ctor public BrightnessThrottlingPoint();
+ method @NonNull public final java.math.BigDecimal getBrightness();
+ method @NonNull public final com.android.server.display.config.ThermalStatus getThermalStatus();
+ method public final void setBrightness(@NonNull java.math.BigDecimal);
+ method public final void setThermalStatus(@NonNull com.android.server.display.config.ThermalStatus);
+ }
+
public class Density {
ctor public Density();
method @NonNull public final java.math.BigInteger getDensity();
@@ -39,6 +52,7 @@
method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease();
method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease();
method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease();
+ method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling();
method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public final void setAmbientLightHorizonLong(java.math.BigInteger);
method public final void setAmbientLightHorizonShort(java.math.BigInteger);
@@ -54,6 +68,7 @@
method public final void setScreenBrightnessRampFastIncrease(java.math.BigDecimal);
method public final void setScreenBrightnessRampSlowDecrease(java.math.BigDecimal);
method public final void setScreenBrightnessRampSlowIncrease(java.math.BigDecimal);
+ method public final void setThermalThrottling(@NonNull com.android.server.display.config.ThermalThrottling);
}
public class DisplayQuirks {
@@ -131,6 +146,12 @@
enum_constant public static final com.android.server.display.config.ThermalStatus shutdown;
}
+ public class ThermalThrottling {
+ ctor public ThermalThrottling();
+ method @NonNull public final com.android.server.display.config.BrightnessThrottlingMap getBrightnessThrottlingMap();
+ method public final void setBrightnessThrottlingMap(@NonNull com.android.server.display.config.BrightnessThrottlingMap);
+ }
+
public class Thresholds {
ctor public Thresholds();
method @NonNull public final com.android.server.display.config.BrightnessThresholds getBrighteningThresholds();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f77aa37..74e04ed 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -183,6 +183,7 @@
import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
+import com.android.server.sensorprivacy.SensorPrivacyService;
import com.android.server.sensors.SensorService;
import com.android.server.signedconfig.SignedConfigService;
import com.android.server.soundtrigger.SoundTriggerService;
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index d562786..717168f 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -90,6 +90,11 @@
private static final String TAG = "MidiService";
+ // These limits are much higher than any normal app should need.
+ private static final int MAX_DEVICE_SERVERS_PER_UID = 16;
+ private static final int MAX_LISTENERS_PER_CLIENT = 16;
+ private static final int MAX_CONNECTIONS_PER_CLIENT = 64;
+
private final Context mContext;
// list of all our clients, keyed by Binder token
@@ -161,6 +166,10 @@
}
public void addListener(IMidiDeviceListener listener) {
+ if (mListeners.size() >= MAX_LISTENERS_PER_CLIENT) {
+ throw new SecurityException(
+ "too many MIDI listeners for UID = " + mUid);
+ }
// Use asBinder() so that we can match it in removeListener().
// The listener proxy objects themselves do not match.
mListeners.put(listener.asBinder(), listener);
@@ -174,6 +183,10 @@
}
public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback) {
+ if (mDeviceConnections.size() >= MAX_CONNECTIONS_PER_CLIENT) {
+ throw new SecurityException(
+ "too many MIDI connections for UID = " + mUid);
+ }
DeviceConnection connection = new DeviceConnection(device, this, callback);
mDeviceConnections.put(connection.getToken(), connection);
device.addDeviceConnection(connection);
@@ -902,6 +915,19 @@
IMidiDeviceServer server, ServiceInfo serviceInfo,
boolean isPrivate, int uid, int defaultProtocol) {
+ // Limit the number of devices per app.
+ int deviceCountForApp = 0;
+ for (Device device : mDevicesByInfo.values()) {
+ if (device.getUid() == uid) {
+ deviceCountForApp++;
+ }
+ }
+ if (deviceCountForApp >= MAX_DEVICE_SERVERS_PER_UID) {
+ throw new SecurityException(
+ "too many MIDI devices already created for UID = "
+ + uid);
+ }
+
int id = mNextDeviceId++;
MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts,
inputPortNames, outputPortNames, properties, isPrivate,
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 5220c8f..c5709fc 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -19,6 +19,7 @@
import android.app.PropertyInvalidatedCache
import android.content.ComponentName
import android.content.Context
+import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.android.server.pm.pkg.component.ParsedActivity
import android.os.Binder
@@ -167,12 +168,12 @@
@Parameterized.Parameter(0)
lateinit var params: Params
- private lateinit var testHandler: TestHandler
private lateinit var mockPendingBroadcasts: PendingPackageBroadcasts
private lateinit var mockPkg: AndroidPackage
private lateinit var mockPkgSetting: PackageSetting
private lateinit var service: PackageManagerService
+ private val testHandler = TestHandler(null)
private val userId = UserHandle.getCallingUserId()
private val userIdDifferent = userId + 1
@@ -180,16 +181,16 @@
fun setUpMocks() {
makeTestData()
- testHandler = TestHandler(null)
+ mockPendingBroadcasts = PendingPackageBroadcasts()
+ service = mockService()
+
+ testHandler.clear()
+
if (params.result is Result.ChangedWithoutNotify) {
// Case where the handler already has a message and so another should not be sent.
// This case will verify that only 1 message exists, which is the one added here.
testHandler.sendEmptyMessage(SEND_PENDING_BROADCAST)
}
-
- mockPendingBroadcasts = PendingPackageBroadcasts()
-
- service = mockService()
}
@Test
@@ -201,19 +202,22 @@
when (val result = params.result) {
Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> {
runUpdate()
- verify(mockPkgSetting).overrideNonLocalizedLabelAndIcon(params.componentName!!,
- TEST_LABEL, TEST_ICON, userId)
+ mockPkgSetting.getUserStateOrDefault(userId)
+ .getOverrideLabelIconForComponent(params.componentName!!)
+ .let {
+ assertThat(it?.first).isEqualTo(TEST_LABEL)
+ assertThat(it?.second).isEqualTo(TEST_ICON)
+ }
}
is Result.Exception -> {
assertThrows(result.type) { runUpdate() }
- verify(mockPkgSetting, never()).overrideNonLocalizedLabelAndIcon(
- any<ComponentName>(), any(), anyInt(), anyInt())
}
}
}
@After
fun verifyExpectedResult() {
+ assertServiceInitialized() ?: return
if (params.componentName != null) {
val activityInfo = service.getActivityInfo(params.componentName, 0, userId)
if (activityInfo != null) {
@@ -225,11 +229,14 @@
@After
fun verifyDifferentUserUnchanged() {
+ assertServiceInitialized() ?: return
when (params.result) {
Result.Changed, Result.ChangedWithoutNotify -> {
- val activityInfo = service.getActivityInfo(params.componentName, 0, userIdDifferent)
- assertThat(activityInfo.nonLocalizedLabel).isEqualTo(DEFAULT_LABEL)
- assertThat(activityInfo.icon).isEqualTo(DEFAULT_ICON)
+ // Suppress so that failures in @After don't override the actual test failure
+ @Suppress("UNNECESSARY_SAFE_CALL")
+ val activityInfo = service?.getActivityInfo(params.componentName, 0, userIdDifferent)
+ assertThat(activityInfo?.nonLocalizedLabel).isEqualTo(DEFAULT_LABEL)
+ assertThat(activityInfo?.icon).isEqualTo(DEFAULT_ICON)
}
Result.NotChanged, is Result.Exception -> {}
}.run { /*exhaust*/ }
@@ -237,6 +244,7 @@
@After
fun verifyHandlerHasMessage() {
+ assertServiceInitialized() ?: return
when (params.result) {
is Result.Changed, is Result.ChangedWithoutNotify -> {
assertThat(testHandler.pendingMessages).hasSize(1)
@@ -251,9 +259,10 @@
@After
fun verifyPendingBroadcast() {
+ assertServiceInitialized() ?: return
when (params.result) {
is Result.Changed, Result.ChangedWithoutNotify -> {
- assertThat(mockPendingBroadcasts.get(userId, params.pkgName))
+ assertThat(mockPendingBroadcasts.get(userId, params.pkgName) ?: emptyList<String>())
.containsExactly(params.componentName!!.className)
.inOrder()
}
@@ -271,26 +280,27 @@
.apply(block)
.hideAsFinal()
- private fun makePkgSetting(pkgName: String) = spy(
+ private fun makePkgSetting(pkgName: String, pkg: AndroidPackage) =
PackageSetting(
pkgName, null, File("/test"),
null, null, null, null, 0, 0, 0, 0, null, null, null, null, null,
UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c")
- )
- ) {
- this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp
- }
+ ).apply {
+ if (params.isSystem) {
+ this.flags = this.flags or ApplicationInfo.FLAG_SYSTEM
+ }
+ this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp
+ this.pkg = pkg
+ }
private fun makeTestData() {
mockPkg = makePkg(params.pkgName)
- mockPkgSetting = makePkgSetting(params.pkgName)
+ mockPkgSetting = makePkgSetting(params.pkgName, mockPkg)
if (params.result is Result.NotChanged) {
// If verifying no-op behavior, set the current setting to the test values
mockPkgSetting.overrideNonLocalizedLabelAndIcon(params.componentName!!, TEST_LABEL,
TEST_ICON, userId)
- // Then clear the mock because the line above just incremented it
- clearInvocations(mockPkgSetting)
}
}
@@ -303,9 +313,9 @@
INVALID_PKG to makePkg(INVALID_PKG) { uid = Binder.getCallingUid() + 1 }
)
val mockedPkgSettings = mutableMapOf(
- VALID_PKG to makePkgSetting(VALID_PKG),
- SHARED_PKG to makePkgSetting(SHARED_PKG),
- INVALID_PKG to makePkgSetting(INVALID_PKG)
+ VALID_PKG to makePkgSetting(VALID_PKG, mockedPkgs[VALID_PKG]!!),
+ SHARED_PKG to makePkgSetting(SHARED_PKG, mockedPkgs[SHARED_PKG]!!),
+ INVALID_PKG to makePkgSetting(INVALID_PKG, mockedPkgs[INVALID_PKG]!!)
)
var mockActivity: ParsedActivity? = null
@@ -330,6 +340,8 @@
whenever(this.componentExists(same(it))) { mockActivity != null }
whenever(this.getActivity(same(it))) { mockActivity }
}
+ whenever(this.snapshot()) { this@mockThrowOnUnmocked }
+ whenever(registerObserver(any())).thenCallRealMethod()
}
val mockUserManagerService: UserManagerService = mockThrowOnUnmocked {
val matcher: (Int) -> Boolean = { it == userId || it == userIdDifferent }
@@ -345,6 +357,8 @@
val mockAppsFilter: AppsFilter = mockThrowOnUnmocked {
whenever(this.shouldFilterApplication(anyInt(), any<PackageSetting>(),
any<PackageSetting>(), anyInt())) { false }
+ whenever(this.snapshot()) { this@mockThrowOnUnmocked }
+ whenever(registerObserver(any())).thenCallRealMethod()
}
val mockContext: Context = mockThrowOnUnmocked {
whenever(this.getString(
@@ -354,27 +368,33 @@
PackageManager.PERMISSION_GRANTED
}
}
- val mockSharedLibrariesImpl: SharedLibrariesImpl = mock()
+ val mockSharedLibrariesImpl: SharedLibrariesImpl = mock {
+ whenever(this.snapshot()) { this@mock }
+ }
val mockInjector: PackageManagerServiceInjector = mock {
whenever(this.lock) { PackageManagerTracedLock() }
whenever(this.componentResolver) { mockComponentResolver }
whenever(this.userManagerService) { mockUserManagerService }
- whenever(this.getUserManagerInternal()) { mockUserManagerInternal }
+ whenever(this.userManagerInternal) { mockUserManagerInternal }
whenever(this.settings) { mockSettings }
whenever(this.getLocalService(ActivityTaskManagerInternal::class.java)) {
mockActivityTaskManager
}
whenever(this.appsFilter) { mockAppsFilter }
whenever(this.context) { mockContext }
- whenever(this.getHandler()) { testHandler }
+ whenever(this.handler) { testHandler }
whenever(this.sharedLibrariesImpl) { mockSharedLibrariesImpl }
}
val testParams = PackageManagerServiceTestParams().apply {
this.pendingPackageBroadcasts = mockPendingBroadcasts
this.resolveComponentName = ComponentName("android", ".Test")
this.packages = ArrayMap<String, AndroidPackage>().apply { putAll(mockedPkgs) }
+ this.instantAppRegistry = mock()
}
return PackageManagerService(mockInjector, testParams)
}
+
+ // If service isn't initialized, then test setup failed and @Afters should be skipped
+ private fun assertServiceInitialized() = Unit.takeIf { ::service.isInitialized }
}
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml
index a4de08a..4e0bb36 100644
--- a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<sensor-privacy persistence-version="1" version="1">
<user id="0" enabled="false">
- <individual-sensor-privacy sensor="1" enabled="true" />
- <individual-sensor-privacy sensor="2" enabled="true" />
+ <individual-sensor-privacy sensor="1" enabled="true" last-change="100" />
+ <individual-sensor-privacy sensor="2" enabled="true" last-change="100" />
</user>
</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file7.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file7.xml
new file mode 100644
index 0000000..2d192db
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file7.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="2" version="2">
+ <sensor-state toggle-type="1" user-id="0" sensor="1" state-type="1" last-change="123" />
+ <sensor-state toggle-type="1" user-id="0" sensor="2" state-type="2" last-change="123" />
+</sensor-privacy>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file8.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file8.xml
new file mode 100644
index 0000000..7bb38b4
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file8.xml
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="2" version="2">
+ <sensor-state toggle-type="2" user-id="0" sensor="1" state-type="1" last-change="1234" />
+ <sensor-state toggle-type="2" user-id="0" sensor="2" state-type="2" last-change="1234" />
+</sensor-privacy>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 303f955..bf46f55 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -97,7 +97,7 @@
@Before
public void setUp() {
- System.loadLibrary("activitymanagermockingservicestestjni");
+ System.loadLibrary("mockingservicestestjni");
mHandlerThread = new HandlerThread("");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 1e0f30e..6ae0031 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -169,24 +169,6 @@
mSettingsMap.putAll(mPreExistingSettings)
!mPreExistingSettings.isEmpty()
}
- whenever(mocks.settings.setPackageStoppedStateLPw(any(), any(), anyBoolean(), anyInt())) {
- val pm: PackageManagerService = getArgument(0)
- val pkgSetting = mSettingsMap[getArgument(1)]!!
- val stopped: Boolean = getArgument(2)
- val userId: Int = getArgument(3)
- return@whenever if (pkgSetting.getStopped(userId) != stopped) {
- pkgSetting.setStopped(stopped, userId)
- if (pkgSetting.getNotLaunched(userId)) {
- pkgSetting.installSource.installerPackageName?.let {
- pm.notifyFirstLaunch(pkgSetting.packageName, it, userId)
- }
- pkgSetting.setNotLaunched(false, userId)
- }
- true
- } else {
- false
- }
- }
}
/** Collection of mocks used for PackageManagerService tests. */
@@ -210,7 +192,9 @@
val packageParser: PackageParser2 = mock()
val keySetManagerService: KeySetManagerService = mock()
val packageAbiHelper: PackageAbiHelper = mock()
- val appsFilter: AppsFilter = mock()
+ val appsFilter: AppsFilter = mock {
+ whenever(snapshot()) { this@mock }
+ }
val dexManager: DexManager = mock()
val installer: Installer = mock()
val displayMetrics: DisplayMetrics = mock()
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
index edbfecc..ccfeb4c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
@@ -59,8 +59,7 @@
false /*isEngBuild*/,
false /*isUserDebugBuild*/,
Build.VERSION_CODES.CUR_DEVELOPMENT,
- Build.VERSION.INCREMENTAL,
- false /*snapshotEnabled*/)
+ Build.VERSION.INCREMENTAL)
rule.system().validateFinalState()
return pms
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
index 0820a3c..bbca121 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
@@ -63,8 +63,7 @@
false /*isEngBuild*/,
false /*isUserDebugBuild*/,
Build.VERSION_CODES.CUR_DEVELOPMENT,
- Build.VERSION.INCREMENTAL,
- false /*snapshotEnabled*/)
+ Build.VERSION.INCREMENTAL)
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
index cfc81e6..a6c7bfb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
@@ -170,7 +170,6 @@
false /*isEngBuild*/,
false /*isUserDebugBuild*/,
Build.VERSION_CODES.CUR_DEVELOPMENT,
- Build.VERSION.INCREMENTAL,
- false /*snapshotEnabled*/)
+ Build.VERSION.INCREMENTAL)
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index 2735e3d..b89f36f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -24,6 +24,7 @@
import android.os.storage.StorageManager
import android.util.ArrayMap
import android.util.PackageUtils
+import com.android.internal.util.FunctionalUtils
import com.android.server.SystemConfig.SharedLibraryEntry
import com.android.server.compat.PlatformCompat
import com.android.server.extendedtestutils.wheneverStatic
@@ -34,6 +35,7 @@
import com.android.server.testutils.any
import com.android.server.testutils.eq
import com.android.server.testutils.mock
+import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.nullable
import com.android.server.testutils.spy
import com.android.server.testutils.whenever
@@ -41,11 +43,14 @@
import com.google.common.truth.Truth.assertThat
import libcore.util.HexEncoding
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.anyString
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -96,10 +101,14 @@
mRule.system().stageNominalSystemState()
addExistingPackages()
- val testParams = PackageManagerServiceTestParams().apply {
- packages = mExistingPackages
- }
- mPms = spy(PackageManagerService(mRule.mocks().injector, testParams))
+ mPms = spy(PackageManagerService(mRule.mocks().injector,
+ false /*coreOnly*/,
+ false /*factoryTest*/,
+ MockSystem.DEFAULT_VERSION_INFO.fingerprint,
+ false /*isEngBuild*/,
+ false /*isUserDebugBuild*/,
+ Build.VERSION_CODES.CUR_DEVELOPMENT,
+ Build.VERSION.INCREMENTAL))
mSettings = mRule.mocks().injector.settings
mSharedLibrariesImpl = SharedLibrariesImpl(mPms, mRule.mocks().injector)
mSharedLibrariesImpl.setDeletePackageHelper(mDeletePackageHelper)
@@ -109,7 +118,19 @@
whenever(mRule.mocks().injector.getSystemService(StorageManager::class.java))
.thenReturn(mStorageManager)
whenever(mStorageManager.findPathForUuid(nullable())).thenReturn(mFile)
- doAnswer { it.arguments[0] }.`when`(mPms).resolveInternalPackageNameLPr(any(), any())
+ doAnswer { it.arguments[0] }.`when`(mPms).resolveInternalPackageName(any(), any())
+ doAnswer {
+ it.getArgument<FunctionalUtils.ThrowingConsumer<Computer>>(0).acceptOrThrow(
+ mockThrowOnUnmocked {
+ whenever(sharedLibraries) { mSharedLibrariesImpl.sharedLibraries }
+ whenever(resolveInternalPackageName(anyString(), anyLong())) {
+ mPms.resolveInternalPackageName(getArgument(0), getArgument(1))
+ }
+ whenever(getPackageStateInternal(anyString())) {
+ mPms.getPackageStateInternal(getArgument(0))
+ }
+ })
+ }.`when`(mPms).executeWithConsistentComputer(any())
whenever(mDeletePackageHelper.deletePackageX(any(), any(), any(), any(), any()))
.thenReturn(PackageManager.DELETE_SUCCEEDED)
whenever(mRule.mocks().injector.compatibility).thenReturn(mPlatformCompat)
@@ -232,6 +253,7 @@
assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME))
}
+ @Ignore("b/216603387")
@Test
fun updateSharedLibraries_withStaticLibPackage() {
val testPackageSetting = mExistingSettings[STATIC_LIB_PACKAGE_NAME]!!
@@ -244,6 +266,7 @@
assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME))
}
+ @Ignore("b/216603387")
@Test
fun updateSharedLibraries_withConsumerPackage() {
val testPackageSetting = mExistingSettings[CONSUMER_PACKAGE_NAME]!!
@@ -257,6 +280,7 @@
assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(STATIC_LIB_PACKAGE_NAME))
}
+ @Ignore("b/216603387")
@Test
fun updateAllSharedLibraries() {
mExistingSettings.forEach {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index fe7e2d9..ac406b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -128,7 +128,7 @@
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
- verify(pms).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID))
+ verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
nullable(), nullable(), nullable())
@@ -214,7 +214,7 @@
null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
testHandler.flush()
- verify(pms, times(2)).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID))
+ verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
nullable(), nullable(), nullable())
@@ -295,7 +295,7 @@
{ suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE }, TEST_USER_ID)
testHandler.flush()
- verify(pms, times(2)).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID))
+ verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
nullable(), nullable(), nullable())
@@ -499,8 +499,7 @@
false /*isEngBuild*/,
false /*isUserDebugBuild*/,
Build.VERSION_CODES.CUR_DEVELOPMENT,
- Build.VERSION.INCREMENTAL,
- false /*snapshotEnabled*/)
+ Build.VERSION.INCREMENTAL)
rule.system().validateFinalState()
return pms
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
index 38f01b5..64e8613 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
@@ -16,43 +16,47 @@
package com.android.server.sensorprivacy;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
+import static android.hardware.SensorPrivacyManager.ToggleTypes.HARDWARE;
+import static android.hardware.SensorPrivacyManager.ToggleTypes.SOFTWARE;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.AppOpsManager;
-import android.app.AppOpsManagerInternal;
import android.content.Context;
-import android.content.pm.UserInfo;
+import android.hardware.SensorPrivacyManager;
import android.os.Environment;
-import android.telephony.TelephonyManager;
+import android.os.Handler;
import android.testing.AndroidTestingRunner;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.LocalServices;
-import com.android.server.SensorPrivacyService;
-import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
-import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
+import org.mockito.ArgumentCaptor;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
-import java.util.concurrent.CompletableFuture;
+import java.nio.file.StandardCopyOption;
@RunWith(AndroidTestingRunner.class)
public class SensorPrivacyServiceMockingTest {
@@ -71,6 +75,10 @@
String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 5);
public static final String PERSISTENCE_FILE6 =
String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 6);
+ public static final String PERSISTENCE_FILE7 =
+ String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 7);
+ public static final String PERSISTENCE_FILE8 =
+ String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 8);
public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE =
"SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml";
@@ -81,176 +89,281 @@
public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE =
"SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml";
- private Context mContext;
- @Mock
- private AppOpsManager mMockedAppOpsManager;
- @Mock
- private AppOpsManagerInternal mMockedAppOpsManagerInternal;
- @Mock
- private UserManagerInternal mMockedUserManagerInternal;
- @Mock
- private ActivityManager mMockedActivityManager;
- @Mock
- private ActivityTaskManager mMockedActivityTaskManager;
- @Mock
- private TelephonyManager mMockedTelephonyManager;
+ Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ String mDataDir = mContext.getApplicationInfo().dataDir;
+
+ @Before
+ public void setUp() {
+ new File(mDataDir, "sensor_privacy.xml").delete();
+ new File(mDataDir, "sensor_privacy_impl.xml").delete();
+ }
@Test
- public void testServiceInit() throws IOException {
+ public void testMigration1() throws IOException {
+ PersistedState ps = migrateFromFile(PERSISTENCE_FILE1);
+
+ assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertTrue(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+
+ assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ }
+
+ @Test
+ public void testMigration2() throws IOException {
+ PersistedState ps = migrateFromFile(PERSISTENCE_FILE2);
+
+ assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertTrue(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+
+ assertTrue(ps.getState(SOFTWARE, 10, MICROPHONE).isEnabled());
+ assertFalse(ps.getState(SOFTWARE, 10, CAMERA).isEnabled());
+
+ assertNull(ps.getState(SOFTWARE, 11, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 11, CAMERA));
+
+ assertTrue(ps.getState(SOFTWARE, 12, MICROPHONE).isEnabled());
+ assertNull(ps.getState(SOFTWARE, 12, CAMERA));
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(HARDWARE, 11, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 11, CAMERA));
+ assertNull(ps.getState(HARDWARE, 12, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 12, CAMERA));
+ }
+
+ @Test
+ public void testMigration3() throws IOException {
+ PersistedState ps = migrateFromFile(PERSISTENCE_FILE3);
+
+ assertFalse(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+
+ assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ }
+
+ @Test
+ public void testMigration4() throws IOException {
+ PersistedState ps = migrateFromFile(PERSISTENCE_FILE4);
+
+ assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+
+ assertFalse(ps.getState(SOFTWARE, 10, MICROPHONE).isEnabled());
+ assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ }
+
+ @Test
+ public void testMigration5() throws IOException {
+ PersistedState ps = migrateFromFile(PERSISTENCE_FILE5);
+
+ assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
+ assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+
+ assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
+ assertFalse(ps.getState(SOFTWARE, 10, CAMERA).isEnabled());
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ }
+
+ @Test
+ public void testMigration6() throws IOException {
+ PersistedState ps = migrateFromFile(PERSISTENCE_FILE6);
+
+ assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 0, CAMERA));
+
+ assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ }
+
+ private PersistedState migrateFromFile(String fileName) throws IOException {
MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.WARN)
.spyStatic(LocalServices.class)
.spyStatic(Environment.class)
.startMocking();
-
try {
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
- spyOn(mContext);
+ doReturn(new File(mDataDir)).when(() -> Environment.getDataSystemDirectory());
- doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
- doReturn(mMockedUserManagerInternal)
- .when(() -> LocalServices.getService(UserManagerInternal.class));
- doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
- doReturn(mMockedActivityTaskManager)
- .when(mContext).getSystemService(ActivityTaskManager.class);
- doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
- TelephonyManager.class);
+ UserManagerInternal umi = mock(UserManagerInternal.class);
+ doReturn(umi).when(() -> LocalServices.getService(UserManagerInternal.class));
+ doReturn(new int[] {0}).when(umi).getUserIds();
- String dataDir = mContext.getApplicationInfo().dataDir;
- doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
+ Files.copy(
+ mContext.getAssets().open(fileName),
+ new File(mDataDir, "sensor_privacy.xml").toPath(),
+ StandardCopyOption.REPLACE_EXISTING);
- File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
- onDeviceFile.delete();
-
- // Try all files with one known user
- doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
- doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
- .getUserInfo(0);
- initServiceWithPersistenceFile(onDeviceFile, null);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE1);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE2);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE3);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE4);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE5);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE6);
-
- // Try all files with two known users
- doReturn(new int[]{0, 10}).when(mMockedUserManagerInternal).getUserIds();
- doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
- .getUserInfo(0);
- doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
- .getUserInfo(10);
- initServiceWithPersistenceFile(onDeviceFile, null);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE1);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE2);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE3);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE4);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE5);
- initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE6);
-
+ return PersistedState.fromFile("sensor_privacy_impl.xml");
} finally {
mockitoSession.finishMocking();
}
}
@Test
- public void testServiceInit_AppOpsRestricted_micMute_camMute() throws IOException {
- testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE, true, true);
+ public void testPersistence1Version2() throws IOException {
+ PersistedState ps = getPersistedStateV2(PERSISTENCE_FILE7);
+
+ assertEquals(1, ps.getState(SOFTWARE, 0, MICROPHONE).getState());
+ assertEquals(123L, ps.getState(SOFTWARE, 0, MICROPHONE).getLastChange());
+ assertEquals(2, ps.getState(SOFTWARE, 0, CAMERA).getState());
+ assertEquals(123L, ps.getState(SOFTWARE, 0, CAMERA).getLastChange());
+
+ assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 10, CAMERA));
}
@Test
- public void testServiceInit_AppOpsRestricted_micMute_camUnmute() throws IOException {
- testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE, true, false);
+ public void testPersistence2Version2() throws IOException {
+ PersistedState ps = getPersistedStateV2(PERSISTENCE_FILE8);
+
+ assertEquals(1, ps.getState(HARDWARE, 0, MICROPHONE).getState());
+ assertEquals(1234L, ps.getState(HARDWARE, 0, MICROPHONE).getLastChange());
+ assertEquals(2, ps.getState(HARDWARE, 0, CAMERA).getState());
+ assertEquals(1234L, ps.getState(HARDWARE, 0, CAMERA).getLastChange());
+
+ assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 0, CAMERA));
+ assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+ assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(HARDWARE, 10, CAMERA));
}
- @Test
- public void testServiceInit_AppOpsRestricted_micUnmute_camMute() throws IOException {
- testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE, false, true);
- }
-
- @Test
- public void testServiceInit_AppOpsRestricted_micUnmute_camUnmute() throws IOException {
- testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE, false, false);
- }
-
- private void testServiceInit_AppOpsRestricted(String persistenceFileMicMuteCamMute,
- boolean expectedMicState, boolean expectedCamState)
- throws IOException {
+ private PersistedState getPersistedStateV2(String version2FilePath) throws IOException {
MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.WARN)
.spyStatic(LocalServices.class)
.spyStatic(Environment.class)
.startMocking();
-
try {
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
- spyOn(mContext);
+ doReturn(new File(mDataDir)).when(() -> Environment.getDataSystemDirectory());
+ Files.copy(
+ mContext.getAssets().open(version2FilePath),
+ new File(mDataDir, "sensor_privacy_impl.xml").toPath(),
+ StandardCopyOption.REPLACE_EXISTING);
- doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
- doReturn(mMockedAppOpsManagerInternal)
- .when(() -> LocalServices.getService(AppOpsManagerInternal.class));
- doReturn(mMockedUserManagerInternal)
- .when(() -> LocalServices.getService(UserManagerInternal.class));
- doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
- doReturn(mMockedActivityTaskManager)
- .when(mContext).getSystemService(ActivityTaskManager.class);
- doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
- TelephonyManager.class);
-
- String dataDir = mContext.getApplicationInfo().dataDir;
- doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
-
- File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
- onDeviceFile.delete();
-
- doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
- doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
- .getUserInfo(0);
-
- CompletableFuture<Boolean> micState = new CompletableFuture<>();
- CompletableFuture<Boolean> camState = new CompletableFuture<>();
- doAnswer(invocation -> {
- int code = invocation.getArgument(0);
- boolean restricted = invocation.getArgument(1);
- if (code == AppOpsManager.OP_RECORD_AUDIO) {
- micState.complete(restricted);
- } else if (code == AppOpsManager.OP_CAMERA) {
- camState.complete(restricted);
- }
- return null;
- }).when(mMockedAppOpsManagerInternal).setGlobalRestriction(anyInt(), anyBoolean(),
- any());
-
- initServiceWithPersistenceFile(onDeviceFile, persistenceFileMicMuteCamMute, 0);
-
- Assert.assertTrue(micState.join() == expectedMicState);
- Assert.assertTrue(camState.join() == expectedCamState);
-
+ return PersistedState.fromFile("sensor_privacy_impl.xml");
} finally {
mockitoSession.finishMocking();
}
}
- private void initServiceWithPersistenceFile(File onDeviceFile,
- String persistenceFilePath) throws IOException {
- initServiceWithPersistenceFile(onDeviceFile, persistenceFilePath, -1);
+ @Test
+ public void testGetDefaultState() {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.WARN)
+ .spyStatic(PersistedState.class)
+ .startMocking();
+ try {
+ PersistedState persistedState = mock(PersistedState.class);
+ doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
+ doReturn(null).when(persistedState).getState(anyInt(), anyInt(), anyInt());
+
+ SensorPrivacyStateController sensorPrivacyStateController =
+ getSensorPrivacyStateControllerImpl();
+
+ SensorState micState = sensorPrivacyStateController.getState(SOFTWARE, 0, MICROPHONE);
+ SensorState camState = sensorPrivacyStateController.getState(SOFTWARE, 0, CAMERA);
+
+ assertEquals(SensorPrivacyManager.StateTypes.DISABLED, micState.getState());
+ assertEquals(SensorPrivacyManager.StateTypes.DISABLED, camState.getState());
+ verify(persistedState, times(1)).getState(SOFTWARE, 0, MICROPHONE);
+ verify(persistedState, times(1)).getState(SOFTWARE, 0, CAMERA);
+ } finally {
+ mockitoSession.finishMocking();
+ }
}
- private void initServiceWithPersistenceFile(File onDeviceFile,
- String persistenceFilePath, int startingUserId) throws IOException {
- if (persistenceFilePath != null) {
- Files.copy(mContext.getAssets().open(persistenceFilePath),
- onDeviceFile.toPath());
+ @Test
+ public void testGetSetState() {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.WARN)
+ .spyStatic(PersistedState.class)
+ .startMocking();
+ try {
+ PersistedState persistedState = mock(PersistedState.class);
+ SensorState sensorState = mock(SensorState.class);
+ doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
+ doReturn(sensorState).when(persistedState).getState(SOFTWARE, 0, MICROPHONE);
+ doReturn(SensorPrivacyManager.StateTypes.ENABLED).when(sensorState).getState();
+ doReturn(0L).when(sensorState).getLastChange();
+
+ SensorPrivacyStateController sensorPrivacyStateController =
+ getSensorPrivacyStateControllerImpl();
+
+ SensorState micState = sensorPrivacyStateController.getState(SOFTWARE, 0, MICROPHONE);
+
+ assertEquals(SensorPrivacyManager.StateTypes.ENABLED, micState.getState());
+ assertEquals(0L, micState.getLastChange());
+ } finally {
+ mockitoSession.finishMocking();
}
- SensorPrivacyService service = new SensorPrivacyService(mContext);
- if (startingUserId != -1) {
- SystemService.TargetUser mockedTargetUser =
- ExtendedMockito.mock(SystemService.TargetUser.class);
- doReturn(startingUserId).when(mockedTargetUser).getUserIdentifier();
- service.onUserStarting(mockedTargetUser);
+ }
+
+ @Test
+ public void testSetState() {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.WARN)
+ .spyStatic(PersistedState.class)
+ .startMocking();
+ try {
+ PersistedState persistedState = mock(PersistedState.class);
+ doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
+
+ SensorPrivacyStateController sensorPrivacyStateController =
+ getSensorPrivacyStateControllerImpl();
+
+ sensorPrivacyStateController.setState(SOFTWARE, 0, MICROPHONE, true,
+ mock(Handler.class), changed -> {});
+
+ ArgumentCaptor<SensorState> captor = ArgumentCaptor.forClass(SensorState.class);
+
+ verify(persistedState, times(1)).setState(eq(SOFTWARE), eq(0), eq(MICROPHONE),
+ captor.capture());
+ assertEquals(SensorPrivacyManager.StateTypes.ENABLED, captor.getValue().getState());
+ } finally {
+ mockitoSession.finishMocking();
}
- onDeviceFile.delete();
+ }
+
+ private SensorPrivacyStateController getSensorPrivacyStateControllerImpl() {
+ SensorPrivacyStateControllerImpl.getInstance().resetForTestingImpl();
+ return SensorPrivacyStateControllerImpl.getInstance();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
index e40f543..e1aa08d 100644
--- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalAnswers.returnsArgAt;
import static org.mockito.ArgumentMatchers.any;
@@ -40,6 +41,7 @@
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
+import android.apphibernation.HibernationStats;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -68,6 +70,8 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -126,7 +130,7 @@
mUsageEventListener = mUsageEventListenerCaptor.getValue();
doReturn(mUserInfos).when(mUserManager).getUsers();
-
+ doReturn(true).when(mPackageManagerInternal).canQueryPackage(anyInt(), any());
doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(),
anyInt(), anyBoolean(), anyBoolean(), any(), any());
@@ -376,6 +380,58 @@
assertFalse(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1));
}
+ @Test
+ public void testGetHibernationStatsForUser_getsStatsForPackage() {
+ // GIVEN a package is hibernating globally and for a user
+ mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true);
+ mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true);
+
+ // WHEN we ask for the hibernation stats for the package
+ Map<String, HibernationStats> stats =
+ mAppHibernationService.getHibernationStatsForUser(
+ Set.of(PACKAGE_NAME_1), USER_ID_1);
+
+ // THEN the stats exist for the package
+ assertTrue(stats.containsKey(PACKAGE_NAME_1));
+ }
+
+ @Test
+ public void testGetHibernationStatsForUser_noExceptionThrownWhenPackageDoesntExist() {
+ // WHEN we ask for the hibernation stats for a package that doesn't exist
+ Map<String, HibernationStats> stats =
+ mAppHibernationService.getHibernationStatsForUser(
+ Set.of(PACKAGE_NAME_1), USER_ID_1);
+
+ // THEN no exception is thrown and empty stats are returned
+ assertNotNull(stats);
+ }
+
+ @Test
+ public void testGetHibernationStatsForUser_returnsAllIfNoPackagesSpecified()
+ throws RemoteException {
+ // GIVEN an unlocked user with all packages installed and they're all hibernating
+ UserInfo userInfo =
+ addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3});
+ doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
+ mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
+ mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true);
+ mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
+ mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_2, true);
+ mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_2, USER_ID_2, true);
+ mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_3, true);
+ mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_3, USER_ID_2, true);
+
+ // WHEN we ask for the hibernation stats with no package specified
+ Map<String, HibernationStats> stats =
+ mAppHibernationService.getHibernationStatsForUser(
+ null /* packageNames */, USER_ID_2);
+
+ // THEN all the package stats are returned
+ assertTrue(stats.containsKey(PACKAGE_NAME_1));
+ assertTrue(stats.containsKey(PACKAGE_NAME_2));
+ assertTrue(stats.containsKey(PACKAGE_NAME_3));
+ }
+
/**
* Mock a usage event occurring.
*
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/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
new file mode 100644
index 0000000..0ed90d2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -0,0 +1,321 @@
+/*
+ * 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 static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.BrightnessInfo;
+import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Temperature.ThrottlingStatus;
+import android.os.Temperature;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.BrightnessThrottler.Injector;
+import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BrightnessThrottlerTest {
+ private static final float EPSILON = 0.000001f;
+
+ private Handler mHandler;
+ private TestLooper mTestLooper;
+
+ @Mock IThermalService mThermalServiceMock;
+ @Mock Injector mInjectorMock;
+
+ @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mInjectorMock.getThermalService()).thenReturn(mThermalServiceMock);
+ mTestLooper = new TestLooper();
+ mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ return true;
+ }
+ });
+
+ }
+
+ /////////////////
+ // Test Methods
+ /////////////////
+
+ @Test
+ public void testBrightnessThrottlingData() {
+ List<ThrottlingLevel> singleLevel = new ArrayList<>();
+ singleLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
+
+ List<ThrottlingLevel> validLevels = new ArrayList<>();
+ validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f));
+ validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
+
+ List<ThrottlingLevel> unsortedThermalLevels = new ArrayList<>();
+ unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f));
+ unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f));
+
+ List<ThrottlingLevel> unsortedBrightnessLevels = new ArrayList<>();
+ unsortedBrightnessLevels.add(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f));
+ unsortedBrightnessLevels.add(
+ new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f));
+
+ List<ThrottlingLevel> unsortedLevels = new ArrayList<>();
+ unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
+ unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f));
+
+ List<ThrottlingLevel> invalidLevel = new ArrayList<>();
+ invalidLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ PowerManager.BRIGHTNESS_MAX + EPSILON));
+
+ // Test invalid data
+ BrightnessThrottlingData data;
+ data = BrightnessThrottlingData.create((List<ThrottlingLevel>)null);
+ assertEquals(data, null);
+ data = BrightnessThrottlingData.create((BrightnessThrottlingData)null);
+ assertEquals(data, null);
+ data = BrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>());
+ assertEquals(data, null);
+ data = BrightnessThrottlingData.create(unsortedThermalLevels);
+ assertEquals(data, null);
+ data = BrightnessThrottlingData.create(unsortedBrightnessLevels);
+ assertEquals(data, null);
+ data = BrightnessThrottlingData.create(unsortedLevels);
+ assertEquals(data, null);
+ data = BrightnessThrottlingData.create(invalidLevel);
+ assertEquals(data, null);
+
+ // Test valid data
+ data = BrightnessThrottlingData.create(singleLevel);
+ assertNotEquals(data, null);
+ assertThrottlingLevelsEquals(singleLevel, data.throttlingLevels);
+
+ data = BrightnessThrottlingData.create(validLevels);
+ assertNotEquals(data, null);
+ assertThrottlingLevelsEquals(validLevels, data.throttlingLevels);
+ }
+
+ @Test
+ public void testThrottlingUnsupported() throws Exception {
+ final BrightnessThrottler throttler = createThrottlerUnsupported();
+ assertFalse(throttler.deviceSupportsThrottling());
+
+ // Thermal listener shouldn't be registered if throttling is unsupported
+ verify(mInjectorMock, never()).getThermalService();
+
+ // Ensure that brightness is uncapped when the device doesn't support throttling
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ }
+
+ @Test
+ public void testThrottlingSingleLevel() throws Exception {
+ final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ 0.25f);
+
+ List<ThrottlingLevel> levels = new ArrayList<>();
+ levels.add(level);
+ final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+ final BrightnessThrottler throttler = createThrottlerSupported(data);
+ assertTrue(throttler.deviceSupportsThrottling());
+
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+ // Set status just high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Set status more than high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Return to the lower throttling level
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Cool down
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
+ throttler.getBrightnessMaxReason());
+ }
+
+ @Test
+ public void testThrottlingMultiLevel() throws Exception {
+ final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE,
+ 0.62f);
+ final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ 0.25f);
+
+ List<ThrottlingLevel> levels = new ArrayList<>();
+ levels.add(levelLo);
+ levels.add(levelHi);
+ final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+ final BrightnessThrottler throttler = createThrottlerSupported(data);
+ assertTrue(throttler.deviceSupportsThrottling());
+
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+ // Set status just high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Set status to an intermediate throttling level
+ listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Set status to the highest configured throttling level
+ listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Set status to exceed the highest configured throttling level
+ listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Return to an intermediate throttling level
+ listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
+ mTestLooper.dispatchAll();
+ assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Return to the lowest configured throttling level
+ listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+ throttler.getBrightnessMaxReason());
+
+ // Cool down
+ listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+ assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+ }
+
+ private void assertThrottlingLevelsEquals(
+ List<ThrottlingLevel> expected,
+ List<ThrottlingLevel> actual) {
+ assertEquals(expected.size(), actual.size());
+
+ for (int i = 0; i < expected.size(); i++) {
+ ThrottlingLevel expectedLevel = expected.get(i);
+ ThrottlingLevel actualLevel = actual.get(i);
+
+ assertEquals(expectedLevel.thermalStatus, actualLevel.thermalStatus);
+ assertEquals(expectedLevel.brightness, actualLevel.brightness, 0.0f);
+ }
+ }
+
+ private BrightnessThrottler createThrottlerUnsupported() {
+ return new BrightnessThrottler(mInjectorMock, mHandler, null, () -> {});
+ }
+
+ private BrightnessThrottler createThrottlerSupported(BrightnessThrottlingData data) {
+ assertNotNull(data);
+ return new BrightnessThrottler(mInjectorMock, mHandler, data, () -> {});
+ }
+
+ private Temperature getSkinTemp(@ThrottlingStatus int status) {
+ return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 418831f..40c0392 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -1461,7 +1461,8 @@
// Turn on HBM, with brightness in the HBM range
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, hbmRefreshRate);
@@ -1469,7 +1470,8 @@
// Turn on HBM, with brightness below the HBM range
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1477,7 +1479,8 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1485,7 +1488,8 @@
// Turn on HBM, with brightness in the HBM range
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, hbmRefreshRate);
@@ -1493,7 +1497,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1501,7 +1505,8 @@
// Turn on HBM, with brightness below the HBM range
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1509,7 +1514,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1580,7 +1585,7 @@
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, initialRefreshRate);
@@ -1598,7 +1603,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1606,7 +1611,7 @@
// Turn HBM on again and ensure the updated vote value stuck
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, updatedRefreshRate);
@@ -1622,7 +1627,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1654,7 +1659,7 @@
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1662,7 +1667,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1694,7 +1699,7 @@
// Turn on HBM when HBM is supported; expect a valid transition point and a vote.
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, 60.0f);
@@ -1702,7 +1707,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1711,7 +1716,7 @@
// no vote.
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- HBM_TRANSITION_POINT_INVALID));
+ HBM_TRANSITION_POINT_INVALID, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1720,7 +1725,7 @@
// no vote.
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
- HBM_TRANSITION_POINT_INVALID));
+ HBM_TRANSITION_POINT_INVALID, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1728,7 +1733,7 @@
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1737,7 +1742,8 @@
private void setHbmAndAssertRefreshRate(
DisplayModeDirector director, DisplayListener listener, int mode, float rr) {
when(mInjector.getBrightnessInfo(DISPLAY_ID))
- .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT));
+ .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
@@ -1817,7 +1823,7 @@
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
- TRANSITION_POINT));
+ TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, 60.f);
@@ -1978,7 +1984,8 @@
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f,
- BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT));
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT,
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
listener.onDisplayChanged(DISPLAY_ID);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 4bb5d74..b7af010 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import static android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+import static android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
@@ -201,7 +203,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
// Verify we are in HBM
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -233,7 +235,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
// Verify we are in HBM
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -258,18 +260,18 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
// Verify we are in HBM
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
- hbmc.onBrightnessChanged(TRANSITION_POINT - 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT - 0.01f);
advanceTime(1);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -288,13 +290,13 @@
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
// Go into HBM for half the allowed window
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
// Move lux below threshold (ending first event);
hbmc.onAmbientLuxChange(MINIMUM_LUX - 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
// Move up some amount of time so that there's still time in the window even after a
@@ -304,7 +306,7 @@
// Go into HBM for just under the second half of allowed window
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 1);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 1);
advanceTime((TIME_ALLOWED_IN_WINDOW_MILLIS / 2) - 1);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -434,7 +436,7 @@
float brightness = 0.5f;
float expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT,
DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range
- hbmc.onBrightnessChanged(brightness);
+ hbmcOnBrightnessChanged(hbmc, brightness);
advanceTime(0);
assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
@@ -442,21 +444,21 @@
brightness = 0.33f;
expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT,
DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range
- hbmc.onBrightnessChanged(brightness);
+ hbmcOnBrightnessChanged(hbmc, brightness);
advanceTime(0);
assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
// Try the min value
brightness = DEFAULT_MIN;
expectedHdrBrightness = DEFAULT_MIN;
- hbmc.onBrightnessChanged(brightness);
+ hbmcOnBrightnessChanged(hbmc, brightness);
advanceTime(0);
assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
// Try the max value
brightness = TRANSITION_POINT;
expectedHdrBrightness = DEFAULT_MAX;
- hbmc.onBrightnessChanged(brightness);
+ hbmcOnBrightnessChanged(hbmc, brightness);
advanceTime(0);
assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
}
@@ -467,7 +469,7 @@
final int displayStatsId = mDisplayUniqueId.hashCode();
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
- hbmc.onBrightnessChanged(TRANSITION_POINT);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
advanceTime(0);
@@ -489,7 +491,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
// Verify Stats HBM_ON_SUNLIGHT
verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
@@ -506,12 +508,12 @@
}
@Test
- public void tetHbmStats_NbmHdrNoReport() {
+ public void testHbmStats_NbmHdrNoReport() {
final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
final int displayStatsId = mDisplayUniqueId.hashCode();
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
- hbmc.onBrightnessChanged(DEFAULT_MIN);
+ hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/);
advanceTime(0);
@@ -524,7 +526,27 @@
}
@Test
- public void testHbmStats_ThermalOff() throws Exception {
+ public void testHbmStats_HighLuxLowBrightnessNoReport() {
+ final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+ final int displayStatsId = mDisplayUniqueId.hashCode();
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+ hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+ hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
+ advanceTime(0);
+ // verify in HBM sunlight mode
+ assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+
+ // Verify Stats HBM_ON_SUNLIGHT not report
+ verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+ anyInt());
+ }
+
+ // Test reporting of thermal throttling when triggered by HighBrightnessModeController's
+ // internal thermal throttling.
+ @Test
+ public void testHbmStats_InternalThermalOff() throws Exception {
final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
final int displayStatsId = mDisplayUniqueId.hashCode();
@@ -534,7 +556,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(1);
verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
@@ -548,6 +570,37 @@
eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
}
+ // Test reporting of thermal throttling when triggered externally through
+ // HighBrightnessModeController.onBrightnessChanged()
+ @Test
+ public void testHbmStats_ExternalThermalOff() throws Exception {
+ final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+ final int displayStatsId = mDisplayUniqueId.hashCode();
+ final float hbmBrightness = TRANSITION_POINT + 0.01f;
+ final float nbmBrightness = TRANSITION_POINT - 0.01f;
+
+ hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+ hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+ // Brightness is unthrottled, HBM brightness granted
+ hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_NONE);
+ advanceTime(1);
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+ // Brightness is thermally throttled, HBM brightness denied (NBM brightness granted)
+ hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_THERMAL);
+ advanceTime(1);
+ // We expect HBM mode to remain set to sunlight, indicating that HBMC *allows* this mode.
+ // However, we expect the HBM state reported by HBMC to be off, since external thermal
+ // throttling (reported to HBMC through onBrightnessChanged()) lowers brightness to below
+ // the HBM transition point.
+ assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+ verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+ eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
+ }
+
@Test
public void testHbmStats_TimeOut() {
final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
@@ -555,7 +608,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(0);
verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
@@ -576,7 +629,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(0);
verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
@@ -595,7 +648,7 @@
hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
- hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
+ hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
advanceTime(0);
verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
@@ -649,4 +702,8 @@
private Temperature getSkinTemp(@ThrottlingStatus int status) {
return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
}
+
+ private void hbmcOnBrightnessChanged(HighBrightnessModeController hbmc, float brightness) {
+ hbmc.onBrightnessChanged(brightness, brightness, BRIGHTNESS_MAX_REASON_NONE);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index e80721a..94cf20f 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -51,10 +51,9 @@
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.NetworkStats.METERED_NO;
import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_CARRIER;
import static android.net.NetworkTemplate.MATCH_MOBILE;
-import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
-import static android.net.NetworkTemplate.buildTemplateWifi;
-import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
@@ -205,6 +204,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@@ -229,10 +229,12 @@
private static final int TEST_SUB_ID = 42;
private static final Network TEST_NETWORK = mock(Network.class, CALLS_REAL_METHODS);
-
- private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_WIFI_NETWORK_KEY);
+ private static NetworkTemplate sTemplateWifi = new NetworkTemplate.Builder(MATCH_WIFI)
+ .setWifiNetworkKeys(Set.of(TEST_WIFI_NETWORK_KEY)).build();
private static NetworkTemplate sTemplateCarrierMetered =
- buildTemplateCarrierMetered(TEST_IMSI);
+ new NetworkTemplate.Builder(MATCH_CARRIER)
+ .setSubscriberIds(Set.of(TEST_IMSI))
+ .setMeteredness(METERED_YES).build();
/**
* Path on assets where files used by {@link NetPolicyXml} are located.
@@ -1160,11 +1162,12 @@
mPolicyListener.expect().onMeteredIfacesChanged(any());
setNetworkPolicies(new NetworkPolicy(
- sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, false));
+ sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, DataUnit.MEBIBYTES.toBytes(1),
+ DataUnit.MEBIBYTES.toBytes(2), false));
mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
- (2 * MB_IN_BYTES) - 512);
+ DataUnit.MEBIBYTES.toBytes(2) - 512);
}
@Test
@@ -1252,7 +1255,7 @@
reset(mTelephonyManager, mNetworkManager, mNotifManager);
TelephonyManager tmSub = expectMobileDefaults();
- mService.snoozeLimit(NetworkTemplate.buildTemplateCarrierMetered(TEST_IMSI));
+ mService.snoozeLimit(sTemplateCarrierMetered);
mService.updateNetworks();
verify(tmSub, atLeastOnce()).setPolicyDataEnabled(true);
@@ -1955,7 +1958,7 @@
assertEquals("Unexpected number of network policies", 1, policies.length);
NetworkPolicy actualPolicy = policies[0];
assertEquals("Unexpected template match rule in network policies",
- NetworkTemplate.MATCH_WIFI,
+ MATCH_WIFI,
actualPolicy.template.getMatchRule());
assertEquals("Unexpected subscriberIds size in network policies",
actualPolicy.template.getSubscriberIds().size(), 0);
@@ -2026,7 +2029,10 @@
private static NetworkPolicy buildFakeCarrierPolicy(int cycleDay, long warningBytes,
long limitBytes, boolean inferred) {
- final NetworkTemplate template = buildTemplateCarrierMetered(FAKE_SUBSCRIBER_ID);
+ // TODO: Refactor this to use sTemplateCarrierMetered.
+ final NetworkTemplate template = new NetworkTemplate.Builder(MATCH_CARRIER)
+ .setSubscriberIds(Set.of(FAKE_SUBSCRIBER_ID))
+ .setMeteredness(METERED_YES).build();
return new NetworkPolicy(template, cycleDay, TimeZone.getDefault().getID(), warningBytes,
limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, inferred);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 6c9a60a..ac83642 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -315,18 +315,19 @@
.setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND)
.build();
- ps1.addOrUpdateSuspension("suspendingPackage1", dialogInfo1, appExtras1, launcherExtras1,
- 0);
- ps1.addOrUpdateSuspension("suspendingPackage2", dialogInfo2, appExtras2, launcherExtras2,
- 0);
+ ps1.modifyUserState(0).putSuspendParams( "suspendingPackage1",
+ SuspendParams.getInstanceOrNull(dialogInfo1, appExtras1, launcherExtras1));
+ ps1.modifyUserState(0).putSuspendParams( "suspendingPackage2",
+ SuspendParams.getInstanceOrNull(dialogInfo2, appExtras2, launcherExtras2));
settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
watcher.verifyChangeReported("put package 1");
- ps2.addOrUpdateSuspension("suspendingPackage3", null, appExtras1, null, 0);
+ ps2.modifyUserState(0).putSuspendParams( "suspendingPackage3",
+ SuspendParams.getInstanceOrNull(null, appExtras1, null));
settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
watcher.verifyChangeReported("put package 2");
- ps3.removeSuspension("irrelevant", 0);
+ ps3.modifyUserState(0).removeSuspension("irrelevant");
settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3);
watcher.verifyChangeReported("put package 3");
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
index 1e4134e..6c72369 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
@@ -41,6 +41,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -323,30 +324,31 @@
}
@Test
public void testPackageUseReasons() throws Exception {
- final PackageStateUnserialized testState1 = new PackageStateUnserialized();
+ PackageSetting packageSetting = Mockito.mock(PackageSetting.class);
+ final PackageStateUnserialized testState1 = new PackageStateUnserialized(packageSetting);
testState1.setLastPackageUsageTimeInMills(-1, 10L);
assertLastPackageUsageUnset(testState1);
- final PackageStateUnserialized testState2 = new PackageStateUnserialized();
+ final PackageStateUnserialized testState2 = new PackageStateUnserialized(packageSetting);
testState2.setLastPackageUsageTimeInMills(
PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT, 20L);
assertLastPackageUsageUnset(testState2);
- final PackageStateUnserialized testState3 = new PackageStateUnserialized();
+ final PackageStateUnserialized testState3 = new PackageStateUnserialized(packageSetting);
testState3.setLastPackageUsageTimeInMills(Integer.MAX_VALUE, 30L);
assertLastPackageUsageUnset(testState3);
- final PackageStateUnserialized testState4 = new PackageStateUnserialized();
+ final PackageStateUnserialized testState4 = new PackageStateUnserialized(packageSetting);
testState4.setLastPackageUsageTimeInMills(0, 40L);
assertLastPackageUsageSet(testState4, 0, 40L);
- final PackageStateUnserialized testState5 = new PackageStateUnserialized();
+ final PackageStateUnserialized testState5 = new PackageStateUnserialized(packageSetting);
testState5.setLastPackageUsageTimeInMills(
PackageManager.NOTIFY_PACKAGE_USE_CONTENT_PROVIDER, 50L);
assertLastPackageUsageSet(
testState5, PackageManager.NOTIFY_PACKAGE_USE_CONTENT_PROVIDER, 50L);
- final PackageStateUnserialized testState6 = new PackageStateUnserialized();
+ final PackageStateUnserialized testState6 = new PackageStateUnserialized(packageSetting);
testState6.setLastPackageUsageTimeInMills(
PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT - 1, 60L);
assertLastPackageUsageSet(
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/servicestests/utils/com/android/server/testutils/TestHandler.java b/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java
index a74615d..22d383a 100644
--- a/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java
@@ -107,6 +107,13 @@
}
}
+ /**
+ * Deletes all messages in queue.
+ */
+ public void clear() {
+ mMessages.clear();
+ }
+
public PriorityQueue<MsgInfo> getPendingMessages() {
return new PriorityQueue<>(mMessages);
}
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/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index ea03250..efc9a49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -106,7 +106,6 @@
import android.app.ActivityTaskManager;
import android.app.WindowConfiguration;
-import android.app.servertransaction.FixedRotationAdjustmentsItem;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Point;
@@ -1666,34 +1665,6 @@
}
@Test
- public void testClearIntermediateFixedRotationAdjustments() throws RemoteException {
- final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
- mDisplayContent.setFixedRotationLaunchingApp(activity,
- (mDisplayContent.getRotation() + 1) % 4);
- // Create a window so FixedRotationAdjustmentsItem can be sent.
- createWindow(null, TYPE_APPLICATION_STARTING, activity, "AppWin");
- final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
- activity2.setVisible(false);
- clearInvocations(mAtm.getLifecycleManager());
- // The first activity has applied fixed rotation but the second activity becomes the top
- // before the transition is done and it has the same rotation as display, so the dispatched
- // rotation adjustment of first activity must be cleared.
- mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(activity2,
- false /* checkOpening */);
-
- final ArgumentCaptor<FixedRotationAdjustmentsItem> adjustmentsCaptor =
- ArgumentCaptor.forClass(FixedRotationAdjustmentsItem.class);
- verify(mAtm.getLifecycleManager(), atLeastOnce()).scheduleTransaction(
- eq(activity.app.getThread()), adjustmentsCaptor.capture());
- // The transformation is kept for animation in real case.
- assertTrue(activity.hasFixedRotationTransform());
- final FixedRotationAdjustmentsItem clearAdjustments = FixedRotationAdjustmentsItem.obtain(
- activity.token, null /* fixedRotationAdjustments */);
- // The captor may match other items. The first one must be the item to clear adjustments.
- assertEquals(clearAdjustments, adjustmentsCaptor.getAllValues().get(0));
- }
-
- @Test
public void testRemoteRotation() {
DisplayContent dc = createNewDisplay();
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
index aa01f31..e655013 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
@@ -16,6 +16,8 @@
package com.android.server.usage;
+import static com.android.server.usage.UsageStatsService.DEBUG_RESPONSE_STATS;
+
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -23,9 +25,11 @@
import android.annotation.UserIdInt;
import android.app.usage.BroadcastResponseStats;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
@@ -65,10 +69,22 @@
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,
@ElapsedRealtimeLong long timestampMs) {
+ if (DEBUG_RESPONSE_STATS) {
+ Slog.d(TAG, TextUtils.formatSimple(
+ "reportBroadcastDispatchEvent; srcUid=%d, tgtPkg=%s, tgtUsr=%d, id=%d, ts=%s",
+ sourceUid, targetPackage, targetUser, idForResponseEvent,
+ TimeUtils.formatDuration(timestampMs)));
+ }
synchronized (mLock) {
final LongSparseArray<BroadcastEvent> broadcastEvents =
getOrCreateBroadcastEventsLocked(targetPackage, targetUser);
@@ -99,6 +115,12 @@
private void reportNotificationEvent(@NotificationEvent int event,
@NonNull String packageName, UserHandle user, @ElapsedRealtimeLong long timestampMs) {
+ if (DEBUG_RESPONSE_STATS) {
+ Slog.d(TAG, TextUtils.formatSimple(
+ "reportNotificationEvent; event=<%s>, pkg=%s, usr=%d, ts=%s",
+ notificationEventToString(event), packageName, user.getIdentifier(),
+ TimeUtils.formatDuration(timestampMs)));
+ }
// TODO (206518114): Store last N events to dump for debugging purposes.
synchronized (mLock) {
final LongSparseArray<BroadcastEvent> broadcastEvents =
@@ -116,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);
@@ -288,6 +309,20 @@
return userResponseStats.getOrCreateBroadcastResponseStats(broadcastEvent);
}
+ @NonNull
+ private String notificationEventToString(@NotificationEvent int event) {
+ switch (event) {
+ case NOTIFICATION_EVENT_POSTED:
+ return "posted";
+ case NOTIFICATION_EVENT_UPDATED:
+ return "updated";
+ case NOTIFICATION_EVENT_CANCELLED:
+ return "cancelled";
+ default:
+ return String.valueOf(event);
+ }
+ }
+
void dump(@NonNull IndentingPrintWriter ipw) {
ipw.println("Broadcast response stats:");
ipw.increaseIndent();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index e28839e..6906f20 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -89,6 +89,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -141,6 +142,7 @@
= SystemProperties.getBoolean("persist.debug.time_correction", true);
static final boolean DEBUG = false; // Never submit with true
+ static final boolean DEBUG_RESPONSE_STATS = DEBUG || Log.isLoggable(TAG, Log.DEBUG);
static final boolean COMPRESS_TIME = false;
private static final long TEN_SECONDS = 10 * 1000;
@@ -279,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/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7ba4b11..a74930b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8898,8 +8898,8 @@
sDefaults.putStringArray(
KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] {
"capabilities=eims, retry_interval=1000, maximum_retries=20",
- "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|"
- + "2254, maximum_retries=0", // No retry for those causes
+ "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2252|"
+ + "2253|2254, maximum_retries=0", // No retry for those causes
"capabilities=mms|supl|cbs, retry_interval=2000",
"capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
+ "5000|10000|15000|20000|40000|60000|120000|240000|"
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>