Merge changes from topics "media_metrics_network", "media_metrics_pb_metrics", "media_metrics_track_change" into sc-dev
* changes:
Unhide media metrics APIs part 2: playback metrics
Unhide media metrics APIs part 2: Network event
Unhide media metrics APIs part 2: Track change event
diff --git a/api/Android.bp b/api/Android.bp
index ac2f083..5466bd2 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -64,6 +64,22 @@
}
genrule {
+ name: "frameworks-base-api-current-compat",
+ srcs: [
+ ":android.api.public.latest",
+ ":android-incompatibilities.api.public.latest",
+ ":frameworks-base-api-current.txt",
+ ],
+ out: ["stdout.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 " +
+ "--check-compatibility:api:released $(location :android.api.public.latest) " +
+ "--baseline:compatibility:released $(location :android-incompatibilities.api.public.latest) " +
+ "$(location :frameworks-base-api-current.txt) " +
+ "> $(genDir)/stdout.txt",
+}
+
+genrule {
name: "frameworks-base-api-current.srcjar",
srcs: [
":android.net.ipsec.ike{.public.stubs.source}",
@@ -162,6 +178,24 @@
}
genrule {
+ name: "frameworks-base-api-system-current-compat",
+ srcs: [
+ ":android.api.system.latest",
+ ":android-incompatibilities.api.system.latest",
+ ":frameworks-base-api-current.txt",
+ ":frameworks-base-api-system-current.txt",
+ ],
+ out: ["stdout.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 " +
+ "--check-compatibility:api:released $(location :android.api.system.latest) " +
+ "--check-compatibility:base $(location :frameworks-base-api-current.txt) " +
+ "--baseline:compatibility:released $(location :android-incompatibilities.api.system.latest) " +
+ "$(location :frameworks-base-api-system-current.txt) " +
+ "> $(genDir)/stdout.txt",
+}
+
+genrule {
name: "frameworks-base-api-system-removed.txt",
srcs: [
":android.net.ipsec.ike{.system.removed-api.txt}",
@@ -231,6 +265,27 @@
}
genrule {
+ name: "frameworks-base-api-module-lib-current-compat",
+ srcs: [
+ ":android.api.module-lib.latest",
+ ":android-incompatibilities.api.module-lib.latest",
+ ":frameworks-base-api-current.txt",
+ ":frameworks-base-api-module-lib-current.txt",
+ ],
+ out: ["stdout.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 " +
+ "--check-compatibility:api:released $(location :android.api.module-lib.latest) " +
+ // Note: having "public" be the base of module-lib is not perfect -- it should
+ // ideally be a merged public+system), but this will help when migrating from
+ // MODULE_LIBS -> public.
+ "--check-compatibility:base $(location :frameworks-base-api-current.txt) " +
+ "--baseline:compatibility:released $(location :android-incompatibilities.api.module-lib.latest) " +
+ "$(location :frameworks-base-api-module-lib-current.txt) " +
+ "> $(genDir)/stdout.txt",
+}
+
+genrule {
name: "frameworks-base-api-module-lib-removed.txt",
srcs: [
":android.net.ipsec.ike{.module-lib.removed-api.txt}",
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 3bad889..5d64579 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -26,10 +26,24 @@
tidy_checks_as_errors: [
"modernize-*",
"-modernize-avoid-c-arrays",
+ "-modernize-pass-by-value",
+ "-modernize-replace-disallow-copy-and-assign-macro",
+ "-modernize-use-equals-default",
+ "-modernize-use-nodiscard",
+ "-modernize-use-override",
"-modernize-use-trailing-return-type",
+ "-modernize-use-using",
"android-*",
"misc-*",
+ "-misc-non-private-member-variables-in-classes",
"readability-*",
+ "-readability-braces-around-statements",
+ "-readability-const-return-type",
+ "-readability-convert-member-functions-to-static",
+ "-readability-else-after-return",
+ "-readability-named-parameter",
+ "-readability-redundant-access-specifiers",
+ "-readability-uppercase-literal-suffix",
],
tidy_flags: [
"-system-headers",
diff --git a/core/api/current.txt b/core/api/current.txt
index d28b584..e357687 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -960,6 +960,8 @@
field public static final int maxLines = 16843091; // 0x1010153
field public static final int maxLongVersionCode = 16844163; // 0x1010583
field public static final int maxRecents = 16843846; // 0x1010446
+ field public static final int maxResizeHeight = 16844339; // 0x1010633
+ field public static final int maxResizeWidth = 16844338; // 0x1010632
field public static final int maxRows = 16843059; // 0x1010133
field public static final int maxSdkVersion = 16843377; // 0x1010271
field public static final int maxWidth = 16843039; // 0x101011f
@@ -1394,6 +1396,8 @@
field public static final int tabWidgetStyle = 16842883; // 0x1010083
field public static final int tag = 16842961; // 0x10100d1
field public static final int targetActivity = 16843266; // 0x1010202
+ field public static final int targetCellHeight = 16844341; // 0x1010635
+ field public static final int targetCellWidth = 16844340; // 0x1010634
field public static final int targetClass = 16842799; // 0x101002f
field @Deprecated public static final int targetDescriptions = 16843680; // 0x10103a0
field public static final int targetId = 16843740; // 0x10103dc
@@ -6074,6 +6078,13 @@
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
+ field public static final String EDIT_CONVERSATION = "convo";
+ field public static final String EDIT_IMPORTANCE = "importance";
+ field public static final String EDIT_LAUNCHER = "launcher";
+ field public static final String EDIT_LOCKED_DEVICE = "locked";
+ field public static final String EDIT_SOUND = "sound";
+ field public static final String EDIT_VIBRATION = "vibration";
+ field public static final String EDIT_ZEN = "dnd";
}
public final class NotificationChannelGroup implements android.os.Parcelable {
@@ -7093,6 +7104,7 @@
method public boolean isUniqueDeviceAttestationSupported();
method public boolean isUsbDataSignalingEnabled();
method public boolean isUsingUnifiedPassword(@NonNull android.content.ComponentName);
+ method @NonNull public java.util.List<android.os.UserHandle> listForegroundAffiliatedUsers();
method public void lockNow();
method public void lockNow(int);
method public int logoutUser(@NonNull android.content.ComponentName);
@@ -8421,6 +8433,8 @@
field public int initialKeyguardLayout;
field public int initialLayout;
field @Deprecated public String label;
+ field public int maxResizeHeight;
+ field public int maxResizeWidth;
field public int minHeight;
field public int minResizeHeight;
field public int minResizeWidth;
@@ -8429,6 +8443,8 @@
field @IdRes public int previewLayout;
field public android.content.ComponentName provider;
field public int resizeMode;
+ field public int targetCellHeight;
+ field public int targetCellWidth;
field public int updatePeriodMillis;
field public int widgetCategory;
field public int widgetFeatures;
@@ -12191,6 +12207,7 @@
method public void setAutoRevokePermissionsMode(boolean);
method public void setInstallLocation(int);
method public void setInstallReason(int);
+ method public void setInstallScenario(int);
method public void setMultiPackage();
method public void setOriginatingUid(int);
method public void setOriginatingUri(@Nullable android.net.Uri);
@@ -12413,6 +12430,7 @@
field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
+ field public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY = "android.hardware.keystore.app_attest_key";
field public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = "android.hardware.keystore.limited_use_key";
field public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY = "android.hardware.keystore.single_use_key";
field public static final String FEATURE_LEANBACK = "android.software.leanback";
@@ -12519,6 +12537,10 @@
field public static final int INSTALL_REASON_POLICY = 1; // 0x1
field public static final int INSTALL_REASON_UNKNOWN = 0; // 0x0
field public static final int INSTALL_REASON_USER = 4; // 0x4
+ field public static final int INSTALL_SCENARIO_BULK = 2; // 0x2
+ field public static final int INSTALL_SCENARIO_BULK_SECONDARY = 3; // 0x3
+ field public static final int INSTALL_SCENARIO_DEFAULT = 0; // 0x0
+ field public static final int INSTALL_SCENARIO_FAST = 1; // 0x1
field public static final int MATCH_ALL = 131072; // 0x20000
field public static final int MATCH_APEX = 1073741824; // 0x40000000
field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000
@@ -21621,6 +21643,7 @@
method @android.media.MediaDrm.HdcpLevel public int getConnectedHdcpLevel();
method public android.media.MediaDrm.CryptoSession getCryptoSession(@NonNull byte[], @NonNull String, @NonNull String);
method @NonNull public android.media.MediaDrm.KeyRequest getKeyRequest(@NonNull byte[], @Nullable byte[], @Nullable String, int, @Nullable java.util.HashMap<java.lang.String,java.lang.String>) throws android.media.NotProvisionedException;
+ method @NonNull public java.util.List<android.media.MediaDrm.LogMessage> getLogMessages();
method @android.media.MediaDrm.HdcpLevel public int getMaxHdcpLevel();
method public static int getMaxSecurityLevel();
method public int getMaxSessionCount();
@@ -21729,6 +21752,12 @@
field public static final int STATUS_USABLE_IN_FUTURE = 5; // 0x5
}
+ public static class MediaDrm.LogMessage {
+ field @NonNull public final String message;
+ field public final int priority;
+ field public final long timestampMillis;
+ }
+
public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
method @NonNull public String getDiagnosticInfo();
}
@@ -26924,6 +26953,46 @@
}
+package android.net.vcn {
+
+ public final class VcnConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
+ }
+
+ public static final class VcnConfig.Builder {
+ ctor public VcnConfig.Builder(@NonNull android.content.Context);
+ method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
+ method @NonNull public android.net.vcn.VcnConfig build();
+ }
+
+ public final class VcnGatewayConnectionConfig {
+ method @NonNull public int[] getExposedCapabilities();
+ method @IntRange(from=android.net.vcn.VcnGatewayConnectionConfig.MIN_MTU_V6) public int getMaxMtu();
+ method @NonNull public int[] getRequiredUnderlyingCapabilities();
+ method @NonNull public long[] getRetryInterval();
+ }
+
+ public static final class VcnGatewayConnectionConfig.Builder {
+ ctor public VcnGatewayConnectionConfig.Builder();
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addRequiredUnderlyingCapability(int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeRequiredUnderlyingCapability(int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=android.net.vcn.VcnGatewayConnectionConfig.MIN_MTU_V6) int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryInterval(@NonNull long[]);
+ }
+
+ public class VcnManager {
+ method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException;
+ method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException;
+ }
+
+}
+
package android.nfc {
public class FormatException extends java.lang.Exception {
@@ -31716,6 +31785,7 @@
method public boolean isQuietModeEnabled(android.os.UserHandle);
method public boolean isSystemUser();
method public boolean isUserAGoat();
+ method public boolean isUserForeground();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunning(android.os.UserHandle);
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunningOrStopping(android.os.UserHandle);
method public boolean isUserUnlocked();
@@ -34931,6 +35001,7 @@
field public static final String EXTRA_AUTHORITIES = "authorities";
field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
field public static final String EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED = "android.provider.extra.BIOMETRIC_AUTHENTICATORS_ALLOWED";
+ field public static final String EXTRA_CHANNEL_FILTER_LIST = "android.provider.extra.CHANNEL_FILTER_LIST";
field public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID";
field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
@@ -35271,30 +35342,18 @@
public static final class SimPhonebookContract.SimRecords {
method @NonNull public static android.net.Uri getContentUri(int, int);
+ method @WorkerThread public static int getEncodedNameLength(@NonNull android.content.ContentResolver, @NonNull String);
method @NonNull public static android.net.Uri getItemUri(int, int, int);
- method @NonNull @WorkerThread public static android.provider.SimPhonebookContract.SimRecords.NameValidationResult validateName(@NonNull android.content.ContentResolver, int, int, @NonNull String);
field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2";
field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2";
field public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type";
+ field public static final int ERROR_NAME_UNSUPPORTED = -1; // 0xffffffff
field public static final String NAME = "name";
field public static final String PHONE_NUMBER = "phone_number";
field public static final String RECORD_NUMBER = "record_number";
field public static final String SUBSCRIPTION_ID = "subscription_id";
}
- public static final class SimPhonebookContract.SimRecords.NameValidationResult implements android.os.Parcelable {
- ctor public SimPhonebookContract.SimRecords.NameValidationResult(@NonNull String, @NonNull String, int, int);
- method public int describeContents();
- method public int getEncodedLength();
- method public int getMaxEncodedLength();
- method @NonNull public String getName();
- method @NonNull public String getSanitizedName();
- method public boolean isSupportedCharacter(int);
- method public boolean isValid();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.provider.SimPhonebookContract.SimRecords.NameValidationResult> CREATOR;
- }
-
public class SyncStateContract {
ctor public SyncStateContract();
}
@@ -37178,6 +37237,7 @@
public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
method @Nullable public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+ method @Nullable public String getAttestKeyAlias();
method public byte[] getAttestationChallenge();
method @NonNull public String[] getBlockModes();
method @NonNull public java.util.Date getCertificateNotAfter();
@@ -37212,6 +37272,7 @@
ctor public KeyGenParameterSpec.Builder(@NonNull String, int);
method @NonNull public android.security.keystore.KeyGenParameterSpec build();
method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(@NonNull java.security.spec.AlgorithmParameterSpec);
+ method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestKeyAlias(@Nullable String);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(@NonNull java.util.Date);
@@ -37309,6 +37370,7 @@
field public static final int ORIGIN_SECURELY_IMPORTED = 8; // 0x8
field public static final int ORIGIN_UNKNOWN = 4; // 0x4
field public static final int PURPOSE_AGREE_KEY = 64; // 0x40
+ field public static final int PURPOSE_ATTEST_KEY = 128; // 0x80
field public static final int PURPOSE_DECRYPT = 2; // 0x2
field public static final int PURPOSE_ENCRYPT = 1; // 0x1
field public static final int PURPOSE_SIGN = 4; // 0x4
@@ -39348,16 +39410,22 @@
}
public static class CallScreeningService.CallResponse {
+ method public int getCallComposerAttachmentsToShow();
method public boolean getDisallowCall();
method public boolean getRejectCall();
method public boolean getSilenceCall();
method public boolean getSkipCallLog();
method public boolean getSkipNotification();
+ field public static final int CALL_COMPOSER_ATTACHMENT_LOCATION = 2; // 0x2
+ field public static final int CALL_COMPOSER_ATTACHMENT_PICTURE = 1; // 0x1
+ field public static final int CALL_COMPOSER_ATTACHMENT_PRIORITY = 8; // 0x8
+ field public static final int CALL_COMPOSER_ATTACHMENT_SUBJECT = 4; // 0x4
}
public static class CallScreeningService.CallResponse.Builder {
ctor public CallScreeningService.CallResponse.Builder();
method public android.telecom.CallScreeningService.CallResponse build();
+ method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setCallComposerAttachmentsToShow(int);
method public android.telecom.CallScreeningService.CallResponse.Builder setDisallowCall(boolean);
method public android.telecom.CallScreeningService.CallResponse.Builder setRejectCall(boolean);
method @NonNull public android.telecom.CallScreeningService.CallResponse.Builder setSilenceCall(boolean);
@@ -46601,8 +46669,10 @@
method public long getMetric(int);
field public static final int ANIMATION_DURATION = 2; // 0x2
field public static final int COMMAND_ISSUE_DURATION = 6; // 0x6
+ field public static final int DEADLINE = 13; // 0xd
field public static final int DRAW_DURATION = 4; // 0x4
field public static final int FIRST_DRAW_FRAME = 9; // 0x9
+ field public static final int GPU_DURATION = 12; // 0xc
field public static final int INPUT_HANDLING_DURATION = 1; // 0x1
field public static final int INTENDED_VSYNC_TIMESTAMP = 10; // 0xa
field public static final int LAYOUT_MEASURE_DURATION = 3; // 0x3
@@ -54871,6 +54941,7 @@
method public void setAccessibilityTraversalAfter(@IdRes int, @IdRes int);
method public void setAccessibilityTraversalBefore(@IdRes int, @IdRes int);
method public void setBitmap(@IdRes int, String, android.graphics.Bitmap);
+ method public void setBlendMode(@IdRes int, @NonNull String, @Nullable android.graphics.BlendMode);
method public void setBoolean(@IdRes int, String, boolean);
method public void setBundle(@IdRes int, String, android.os.Bundle);
method public void setByte(@IdRes int, String, byte);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b57fdf1..de02d0b 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -217,6 +217,7 @@
method @NonNull public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection<android.net.LinkAddress>);
method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder);
method public void teardownTestNetwork(@NonNull android.net.Network);
+ field public static final String TEST_TAP_PREFIX = "testtap";
}
public final class UnderlyingNetworkInfo implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7541853..f5366e37 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -14,6 +14,7 @@
field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
field public static final String ACCESS_NETWORK_CONDITIONS = "android.permission.ACCESS_NETWORK_CONDITIONS";
field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
+ field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE";
field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
@@ -197,6 +198,7 @@
field public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
field public static final String READ_NETWORK_USAGE_HISTORY = "android.permission.READ_NETWORK_USAGE_HISTORY";
field public static final String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE";
+ field public static final String READ_PEOPLE_DATA = "android.permission.READ_PEOPLE_DATA";
field public static final String READ_PRINT_SERVICES = "android.permission.READ_PRINT_SERVICES";
field public static final String READ_PRINT_SERVICE_RECOMMENDATIONS = "android.permission.READ_PRINT_SERVICE_RECOMMENDATIONS";
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
@@ -271,6 +273,7 @@
field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
+ field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
@@ -343,6 +346,7 @@
field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
+ field public static final int config_systemContacts = 17039403; // 0x104002b
field public static final int config_systemGallery = 17039399; // 0x1040027
field public static final int config_systemShell = 17039402; // 0x104002a
}
@@ -1275,6 +1279,14 @@
}
+package android.app.people {
+
+ public final class PeopleManager {
+ method @RequiresPermission(android.Manifest.permission.READ_PEOPLE_DATA) public boolean isConversation(@NonNull String, @NonNull String);
+ }
+
+}
+
package android.app.prediction {
public final class AppPredictionContext implements android.os.Parcelable {
@@ -8949,6 +8961,16 @@
package android.permission {
+ public final class AdminPermissionControlParams implements android.os.Parcelable {
+ method public boolean canAdminGrantSensorsPermissions();
+ method public int describeContents();
+ method public int getGrantState();
+ method @NonNull public String getGranteePackageName();
+ method @NonNull public String getPermission();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.permission.AdminPermissionControlParams> CREATOR;
+ }
+
public final class PermissionControllerManager {
method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
@@ -8980,7 +9002,8 @@
method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
- method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull java.util.concurrent.Executor, @NonNull Runnable);
method @BinderThread public void onUpdateUserSensitivePermissionFlags(int, @NonNull Runnable);
@@ -9451,22 +9474,8 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean);
}
- public final class SimPhonebookContract {
- method @NonNull public static String getEfUriPath(int);
- field public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";
- }
-
- public static final class SimPhonebookContract.ElementaryFiles {
- field public static final String EF_ADN_PATH_SEGMENT = "adn";
- field public static final String EF_FDN_PATH_SEGMENT = "fdn";
- field public static final String EF_SDN_PATH_SEGMENT = "sdn";
- field public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files";
- }
-
public static final class SimPhonebookContract.SimRecords {
- field public static final String EXTRA_NAME_VALIDATION_RESULT = "android.provider.extra.NAME_VALIDATION_RESULT";
field public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2";
- field public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name";
}
public static final class Telephony.Carriers implements android.provider.BaseColumns {
@@ -10760,7 +10769,7 @@
method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
method @Nullable public final String getTelecomCallId();
method @Deprecated public void onAudioStateChanged(android.telecom.AudioState);
- method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean);
+ method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean);
method public final void resetConnectionTime();
method public void setCallDirection(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long);
@@ -10937,7 +10946,7 @@
}
public final class RemoteConnection {
- method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean);
+ method @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public void onCallFilteringCompleted(boolean, boolean, @Nullable android.telecom.CallScreeningService.CallResponse, boolean);
method @Deprecated public void setAudioState(android.telecom.AudioState);
}
@@ -13450,8 +13459,8 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addOnPublishStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getUcePublishState() throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeOnPublishStateChangedListener(@NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException;
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, android.Manifest.permission.READ_CONTACTS}) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException;
field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
field public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 1; // 0x1
@@ -14217,10 +14226,10 @@
}
public final class RangingSession implements java.lang.AutoCloseable {
- method public void close();
- method public void reconfigure(@NonNull android.os.PersistableBundle);
- method public void start(@NonNull android.os.PersistableBundle);
- method public void stop();
+ method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void close();
+ method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void reconfigure(@NonNull android.os.PersistableBundle);
+ method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void start(@NonNull android.os.PersistableBundle);
+ method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void stop();
}
public static interface RangingSession.Callback {
@@ -14256,18 +14265,18 @@
}
public final class UwbManager {
- method public long elapsedRealtimeResolutionNanos();
- method public int getAngleOfArrivalSupport();
- method public int getMaxRemoteDevicesPerInitiatorSession();
- method public int getMaxRemoteDevicesPerResponderSession();
- method public int getMaxSimultaneousSessions();
- method @NonNull public android.os.PersistableBundle getSpecificationInfo();
- method @NonNull public java.util.List<java.lang.Integer> getSupportedChannelNumbers();
- method @NonNull public java.util.Set<java.lang.Integer> getSupportedPreambleCodeIndices();
- method public boolean isRangingSupported();
- method @NonNull public AutoCloseable openRangingSession(@NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.uwb.RangingSession.Callback);
- method public void registerAdapterStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.AdapterStateCallback);
- method public void unregisterAdapterStateCallback(@NonNull android.uwb.UwbManager.AdapterStateCallback);
+ method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public long elapsedRealtimeResolutionNanos();
+ method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getAngleOfArrivalSupport();
+ method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getMaxRemoteDevicesPerInitiatorSession();
+ method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getMaxRemoteDevicesPerResponderSession();
+ method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int getMaxSimultaneousSessions();
+ method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public android.os.PersistableBundle getSpecificationInfo();
+ method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public java.util.List<java.lang.Integer> getSupportedChannelNumbers();
+ method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public java.util.Set<java.lang.Integer> getSupportedPreambleCodeIndices();
+ method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public boolean isRangingSupported();
+ method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public AutoCloseable openRangingSession(@NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.uwb.RangingSession.Callback);
+ method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void registerAdapterStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.AdapterStateCallback);
+ method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void unregisterAdapterStateCallback(@NonNull android.uwb.UwbManager.AdapterStateCallback);
field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D = 2; // 0x2
field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL = 3; // 0x3
field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL = 4; // 0x4
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 989025c..ff96f92 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2556,6 +2556,7 @@
public final class InputMethodManager {
method public int getDisplayId();
+ method public boolean hasActiveInputConnection(@Nullable android.view.View);
method public boolean isInputMethodPickerShown();
}
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 5402381..101917b 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -17,11 +17,9 @@
package android.app;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.app.IWallpaperManagerCallback;
-import android.app.ILocalWallpaperColorConsumer;
import android.app.WallpaperInfo;
import android.content.ComponentName;
import android.app.WallpaperColors;
@@ -164,18 +162,6 @@
WallpaperColors getWallpaperColors(int which, int userId, int displayId);
/**
- * @hide
- */
- void removeOnLocalColorsChangedListener(
- in ILocalWallpaperColorConsumer callback, int which, int userId, int displayId);
-
- /**
- * @hide
- */
- void addOnLocalColorsChangedListener(in ILocalWallpaperColorConsumer callback,
- in List<RectF> regions, int which, int userId, int displayId);
-
- /**
* Register a callback to receive color updates from a display
*/
void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId, int displayId);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 323af821..685c222 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -80,6 +80,48 @@
public static final String PLACEHOLDER_CONVERSATION_ID = ":placeholder_id";
/**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing sound, like a tone picker
+ * ({@link #setSound(Uri, AudioAttributes)}).
+ */
+ public static final String EDIT_SOUND = "sound";
+ /**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing vibration ({@link #enableVibration(boolean)},
+ * {@link #setVibrationPattern(long[])}).
+ */
+ public static final String EDIT_VIBRATION = "vibration";
+ /**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing importance ({@link #setImportance(int)}) and/or conversation
+ * priority.
+ */
+ public static final String EDIT_IMPORTANCE = "importance";
+ /**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing behavior on devices that are locked or have a turned off
+ * display ({@link #setLockscreenVisibility(int)}, {@link #enableLights(boolean)},
+ * {@link #setLightColor(int)}).
+ */
+ public static final String EDIT_LOCKED_DEVICE = "locked";
+ /**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing do not disturb bypass {(@link #setBypassDnd(boolean)}) .
+ */
+ public static final String EDIT_ZEN = "dnd";
+ /**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing conversation settings (demoting or restoring a channel to
+ * be a Conversation, changing bubble behavior, or setting the priority of a conversation).
+ */
+ public static final String EDIT_CONVERSATION = "convo";
+ /**
+ * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
+ * that have to do with editing launcher behavior (showing badges)}.
+ */
+ public static final String EDIT_LAUNCHER = "launcher";
+
+ /**
* The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
* limit.
*/
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index d7587bd..ac2f223 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -66,7 +66,6 @@
import android.os.StrictMode;
import android.os.SystemProperties;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
@@ -108,8 +107,6 @@
private static boolean DEBUG = false;
private float mWallpaperXStep = -1;
private float mWallpaperYStep = -1;
- private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
- new RectF(0, 0, 1, 1);
/** {@hide} */
private static final String PROP_WALLPAPER = "ro.config.wallpaper";
@@ -312,8 +309,6 @@
private int mCachedWallpaperUserId;
private Bitmap mDefaultWallpaper;
private Handler mMainLooperHandler;
- private ArrayMap<LocalWallpaperColorConsumer, ILocalWallpaperColorConsumer>
- mLocalColorCallbacks = new ArrayMap<>();
Globals(IWallpaperManager service, Looper looper) {
mService = service;
@@ -355,40 +350,6 @@
}
}
- private ILocalWallpaperColorConsumer wrap(LocalWallpaperColorConsumer callback) {
- ILocalWallpaperColorConsumer callback2 = new ILocalWallpaperColorConsumer.Stub() {
- @Override
- public void onColorsChanged(RectF area, WallpaperColors colors) {
- callback.onColorsChanged(area, colors);
- }
- };
- mLocalColorCallbacks.put(callback, callback2);
- return callback2;
- }
-
- public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
- @NonNull List<RectF> regions, int which, int userId, int displayId) {
- try {
- mService.addOnLocalColorsChangedListener(wrap(callback) , regions, which,
- userId, displayId);
- } catch (RemoteException e) {
- // Can't get colors, connection lost.
- }
- }
-
- public void removeOnColorsChangedListener(
- @NonNull LocalWallpaperColorConsumer callback, int which, int userId,
- int displayId) {
- ILocalWallpaperColorConsumer callback2 = mLocalColorCallbacks.remove(callback);
- if (callback2 == null) return;
- try {
- mService.removeOnLocalColorsChangedListener(
- callback2, which, userId, displayId);
- } catch (RemoteException e) {
- // Can't get colors, connection lost.
- }
- }
-
/**
* Stop listening to wallpaper color events.
*
@@ -1096,29 +1057,6 @@
}
/**
- * @hide
- */
- public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
- List<RectF> regions) throws IllegalArgumentException {
- for (RectF region : regions) {
- if (!LOCAL_COLOR_BOUNDS.contains(region)) {
- throw new IllegalArgumentException("Regions must be within bounds "
- + LOCAL_COLOR_BOUNDS);
- }
- }
- sGlobals.addOnColorsChangedListener(callback, regions, FLAG_SYSTEM,
- mContext.getUserId(), mContext.getDisplayId());
- }
-
- /**
- * @hide
- */
- public void removeOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback) {
- sGlobals.removeOnColorsChangedListener(callback, FLAG_SYSTEM, mContext.getUserId(),
- mContext.getDisplayId());
- }
-
- /**
* Version of {@link #getWallpaperFile(int)} that can access the wallpaper data
* for a given user. The caller must hold the INTERACT_ACROSS_USERS_FULL
* permission to access another user's wallpaper data.
@@ -2264,18 +2202,4 @@
onColorsChanged(colors, which);
}
}
-
- /**
- * Callback to update a consumer with a local color change
- * @hide
- */
- public interface LocalWallpaperColorConsumer {
-
- /**
- * Gets called when a color of an area gets updated
- * @param area
- * @param colors
- */
- void onColorsChanged(RectF area, WallpaperColors colors);
- }
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 05e9dcf..305c224 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1869,13 +1869,13 @@
/**
* Grants access to {@link #setNetworkLoggingEnabled}, {@link #isNetworkLoggingEnabled} and
* {@link #retrieveNetworkLogs}. Once granted the delegated app will start receiving
- * DelegatedAdminReceiver.onNetworkLogsAvailable() callback, and Device owner will no longer
- * receive the DeviceAdminReceiver.onNetworkLogsAvailable() callback.
+ * DelegatedAdminReceiver.onNetworkLogsAvailable() callback, and Device owner or Profile Owner
+ * will no longer receive the DeviceAdminReceiver.onNetworkLogsAvailable() callback.
* There can be at most one app that has this delegation.
* If another app already had delegated network logging access,
* it will lose the delegation when a new app is delegated.
*
- * <p> Can only be granted by Device Owner.
+ * <p> Can only be granted by Device Owner or Profile Owner of a managed profile.
*/
public static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
@@ -13357,6 +13357,7 @@
}
}
}
+
/**
* Returns true if the caller is running on a device where the admin can grant
* permissions related to device sensors.
@@ -13459,4 +13460,22 @@
}
return false;
}
+
+ /**
+ * Gets the list of {@link #isAffiliatedUser() affiliated} users running on foreground.
+ *
+ * @return list of {@link #isAffiliatedUser() affiliated} users running on foreground.
+ *
+ * @throws SecurityException if the calling application is not a device owner
+ */
+ @NonNull
+ public List<UserHandle> listForegroundAffiliatedUsers() {
+ if (mService == null) return Collections.emptyList();
+
+ try {
+ return mService.listForegroundAffiliatedUsers();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 94388cf..91a9f3c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -507,4 +507,6 @@
boolean isUsbDataSignalingEnabled(String callerPackage);
boolean isUsbDataSignalingEnabledForUser(int userId);
boolean canUsbDataSignalingBeDisabled();
+
+ List<UserHandle> listForegroundAffiliatedUsers();
}
diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl
index ebe9f60..d000f3b 100644
--- a/core/java/android/app/people/IPeopleManager.aidl
+++ b/core/java/android/app/people/IPeopleManager.aidl
@@ -49,6 +49,9 @@
/** Removes all the recent conversations and uncaches their cached shortcuts. */
void removeAllRecentConversations();
+ /** Returns whether the shortcutId is backed by a Conversation in People Service. */
+ boolean isConversation(in String packageName, int userId, in String shortcutId);
+
/**
* Returns the last interaction with the specified conversation. If the
* conversation can't be found or no interactions have been recorded, returns 0L.
diff --git a/core/java/android/app/people/PeopleManager.java b/core/java/android/app/people/PeopleManager.java
index de7ba62..d348edb 100644
--- a/core/java/android/app/people/PeopleManager.java
+++ b/core/java/android/app/people/PeopleManager.java
@@ -17,6 +17,8 @@
package android.app.people;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.ParceledListSlice;
@@ -53,6 +55,33 @@
Context.PEOPLE_SERVICE));
}
+ /**
+ * Returns whether a shortcut has a conversation associated.
+ *
+ * <p>Requires android.permission.READ_PEOPLE_DATA permission.
+ *
+ * <p>This method may return different results for the same shortcut over time, as an app adopts
+ * conversation features or if a user hasn't communicated with the conversation associated to
+ * the shortcut in a while, so the result should not be stored and relied on indefinitely by
+ * clients.
+ *
+ * @param packageName name of the package the conversation is part of
+ * @param shortcutId the shortcut id backing the conversation
+ * @return whether the {@shortcutId} is backed by a Conversation.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PEOPLE_DATA)
+ public boolean isConversation(@NonNull String packageName, @NonNull String shortcutId) {
+ Preconditions.checkStringNotEmpty(packageName);
+ Preconditions.checkStringNotEmpty(shortcutId);
+ try {
+ return mService.isConversation(packageName, mContext.getUserId(), shortcutId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Sets or updates a {@link ConversationStatus} for a conversation.
diff --git a/core/java/android/app/smartspace/SmartspaceAction.java b/core/java/android/app/smartspace/SmartspaceAction.java
index 439d851..f17b044 100644
--- a/core/java/android/app/smartspace/SmartspaceAction.java
+++ b/core/java/android/app/smartspace/SmartspaceAction.java
@@ -89,7 +89,7 @@
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
mIntent = in.readTypedObject(Intent.CREATOR);
mUserHandle = in.readTypedObject(UserHandle.CREATOR);
- mExtras = in.readTypedObject(Bundle.CREATOR);
+ mExtras = in.readBundle();
}
private SmartspaceAction(
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 42214d0..d893a5e 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -179,6 +179,44 @@
public int minResizeHeight;
/**
+ * Maximum width (in dp) which the widget can be resized to. This field has no effect if it is
+ * smaller than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
+ *
+ * <p>This field corresponds to the <code>android:maxResizeWidth</code> attribute in the
+ * AppWidget meta-data file.
+ */
+ @SuppressLint("MutableBareField")
+ public int maxResizeWidth;
+
+ /**
+ * Maximum height (in dp) which the widget can be resized to. This field has no effect if it is
+ * smaller than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
+ *
+ * <p>This field corresponds to the <code>android:maxResizeHeight</code> attribute in the
+ * AppWidget meta-data file.
+ */
+ @SuppressLint("MutableBareField")
+ public int maxResizeHeight;
+
+ /**
+ * The default width of a widget when added to a host, in units of launcher grid cells.
+ *
+ * <p>This field corresponds to the <code>android:targetCellWidth</code> attribute in the
+ * AppWidget meta-data file.
+ */
+ @SuppressLint("MutableBareField")
+ public int targetCellWidth;
+
+ /**
+ * The default height of a widget when added to a host, in units of launcher grid cells.
+ *
+ * <p>This field corresponds to the <code>android:targetCellHeight</code> attribute in the
+ * AppWidget meta-data file.
+ */
+ @SuppressLint("MutableBareField")
+ public int targetCellHeight;
+
+ /**
* How often, in milliseconds, that this AppWidget wants to be updated.
* The AppWidget manager may place a limit on how often a AppWidget is updated.
*
@@ -330,6 +368,10 @@
this.minHeight = in.readInt();
this.minResizeWidth = in.readInt();
this.minResizeHeight = in.readInt();
+ this.maxResizeWidth = in.readInt();
+ this.maxResizeHeight = in.readInt();
+ this.targetCellWidth = in.readInt();
+ this.targetCellHeight = in.readInt();
this.updatePeriodMillis = in.readInt();
this.initialLayout = in.readInt();
this.initialKeyguardLayout = in.readInt();
@@ -440,6 +482,10 @@
out.writeInt(this.minHeight);
out.writeInt(this.minResizeWidth);
out.writeInt(this.minResizeHeight);
+ out.writeInt(this.maxResizeWidth);
+ out.writeInt(this.maxResizeHeight);
+ out.writeInt(this.targetCellWidth);
+ out.writeInt(this.targetCellHeight);
out.writeInt(this.updatePeriodMillis);
out.writeInt(this.initialLayout);
out.writeInt(this.initialKeyguardLayout);
@@ -463,8 +509,12 @@
that.provider = this.provider == null ? null : this.provider.clone();
that.minWidth = this.minWidth;
that.minHeight = this.minHeight;
- that.minResizeWidth = this.minResizeHeight;
+ that.minResizeWidth = this.minResizeWidth;
that.minResizeHeight = this.minResizeHeight;
+ that.maxResizeWidth = this.maxResizeWidth;
+ that.maxResizeHeight = this.maxResizeHeight;
+ that.targetCellWidth = this.targetCellWidth;
+ that.targetCellHeight = this.targetCellHeight;
that.updatePeriodMillis = this.updatePeriodMillis;
that.initialLayout = this.initialLayout;
that.initialKeyguardLayout = this.initialKeyguardLayout;
@@ -512,6 +562,8 @@
minHeight = TypedValue.complexToDimensionPixelSize(minHeight, displayMetrics);
minResizeWidth = TypedValue.complexToDimensionPixelSize(minResizeWidth, displayMetrics);
minResizeHeight = TypedValue.complexToDimensionPixelSize(minResizeHeight, displayMetrics);
+ maxResizeWidth = TypedValue.complexToDimensionPixelSize(maxResizeWidth, displayMetrics);
+ maxResizeHeight = TypedValue.complexToDimensionPixelSize(maxResizeHeight, displayMetrics);
}
/**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5166943..10b00f2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4583,6 +4583,14 @@
public static final String AUTOFILL_MANAGER_SERVICE = "autofill";
/**
+ * Official published name of the (internal) text to speech manager service.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String TEXT_TO_SPEECH_MANAGER_SERVICE = "texttospeech";
+
+ /**
* Official published name of the content capture service.
*
* @hide
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 0c0e402..80fecc1 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -167,10 +167,17 @@
*/
public static final int FLAG_CACHE_BUBBLE_SHORTCUTS = 1;
+ /**
+ * Cache shortcuts which are used in People Tile.
+ * @hide
+ */
+ public static final int FLAG_CACHE_PEOPLE_TILE_SHORTCUTS = 2;
+
/** @hide */
@IntDef(flag = false, prefix = { "FLAG_CACHE_" }, value = {
FLAG_CACHE_NOTIFICATION_SHORTCUTS,
FLAG_CACHE_BUBBLE_SHORTCUTS,
+ FLAG_CACHE_PEOPLE_TILE_SHORTCUTS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ShortcutCacheFlags {}
@@ -1179,6 +1186,7 @@
* <ul>
* <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
* <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
+ * <li>{@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS}
* </ul>
* @throws IllegalStateException when the user is locked, or when the {@code user} user
* is locked or not running.
@@ -1209,6 +1217,7 @@
* <ul>
* <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
* <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
+ * <li>{@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS}
* </ul>
* @throws IllegalStateException when the user is locked, or when the {@code user} user
* is locked or not running.
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index f9980bc..567501c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2065,6 +2065,13 @@
this.forceQueryableOverride = true;
}
+ /**
+ * Sets the install scenario for this session, which describes the expected user journey.
+ */
+ public void setInstallScenario(@InstallScenario int installScenario) {
+ this.installScenario = installScenario;
+ }
+
/** {@hide} */
public void dump(IndentingPrintWriter pw) {
pw.printPair("mode", mode);
@@ -2224,7 +2231,7 @@
/** {@hide} */
public @InstallReason int installReason;
/** {@hide} */
- public @InstallReason int installScenario;
+ public @InstallScenario int installScenario;
/** {@hide} */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public long sizeBytes;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a3c3500..fdb00c6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1356,15 +1356,11 @@
/**
* A value to indicate the lack of CUJ information, disabling all installation scenario logic.
- *
- * @hide
*/
public static final int INSTALL_SCENARIO_DEFAULT = 0;
/**
* Installation scenario providing the fastest “install button to launch" experience possible.
- *
- * @hide
*/
public static final int INSTALL_SCENARIO_FAST = 1;
@@ -1381,8 +1377,6 @@
* less optimized applications. The device state (e.g. memory usage or battery status) should
* not be considered when making this decision as those factors are taken into account by the
* Package Manager when acting on the installation scenario.
- *
- * @hide
*/
public static final int INSTALL_SCENARIO_BULK = 2;
@@ -1393,8 +1387,6 @@
* operation that are marked BULK_SECONDARY, the faster the entire bulk operation will be.
*
* See the comments for INSTALL_SCENARIO_BULK for more information.
- *
- * @hide
*/
public static final int INSTALL_SCENARIO_BULK_SECONDARY = 3;
@@ -3677,6 +3669,15 @@
public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY =
"android.hardware.keystore.limited_use_key";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * a Keystore implementation that can create application-specific attestation keys.
+ * See {@link android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias}.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY =
+ "android.hardware.keystore.app_attest_key";
+
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index ce0547f..5f80ba1 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -129,6 +129,12 @@
/** @hide */
public static final int FLAG_HAS_ICON_URI = 1 << 15;
+ /**
+ * TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
+ * need to be aware of the outside world. Replace this with a more extensible solution.
+ * @hide
+ */
+ public static final int FLAG_CACHED_PEOPLE_TILE = 1 << 29;
/**
* TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
@@ -138,7 +144,8 @@
public static final int FLAG_CACHED_BUBBLES = 1 << 30;
/** @hide */
- public static final int FLAG_CACHED_ALL = FLAG_CACHED_NOTIFICATIONS | FLAG_CACHED_BUBBLES;
+ public static final int FLAG_CACHED_ALL =
+ FLAG_CACHED_NOTIFICATIONS | FLAG_CACHED_BUBBLES | FLAG_CACHED_PEOPLE_TILE;
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
@@ -159,6 +166,7 @@
FLAG_HAS_ICON_URI,
FLAG_CACHED_NOTIFICATIONS,
FLAG_CACHED_BUBBLES,
+ FLAG_CACHED_PEOPLE_TILE
})
@Retention(RetentionPolicy.SOURCE)
public @interface ShortcutFlags {}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index ebb3021..a65f36b 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -24,6 +24,7 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.app.ActivityThread;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -849,9 +850,18 @@
attributionTag = context.getAttributionTag();
}
+ // Workaround for old APIs not providing a context
+ String packageName;
+ if (context != null) {
+ packageName = context.getPackageName();
+ } else {
+ packageName = ActivityThread.currentPackageName();
+ }
+
IContextHubClient clientProxy;
try {
- clientProxy = mService.createClient(hubInfo.getId(), clientInterface, attributionTag);
+ clientProxy = mService.createClient(
+ hubInfo.getId(), clientInterface, attributionTag, packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 4961195..92882c4 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -60,7 +60,8 @@
// Creates a client to send and receive messages
IContextHubClient createClient(
- int contextHubId, in IContextHubClientCallback client, in String attributionTag);
+ int contextHubId, in IContextHubClientCallback client, in String attributionTag,
+ in String packageName);
// Creates a PendingIntent-based client to send and receive messages
IContextHubClient createPendingIntentClient(
diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl
index 4f293ee..6a3cb42 100644
--- a/core/java/android/net/vcn/IVcnManagementService.aidl
+++ b/core/java/android/net/vcn/IVcnManagementService.aidl
@@ -18,6 +18,7 @@
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
+import android.net.vcn.IVcnStatusCallback;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
@@ -33,4 +34,7 @@
void addVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
void removeVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(in NetworkCapabilities nc, in LinkProperties lp);
+
+ void registerVcnStatusCallback(in ParcelUuid subscriptionGroup, in IVcnStatusCallback callback, in String opPkgName);
+ void unregisterVcnStatusCallback(in IVcnStatusCallback callback);
}
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/core/java/android/net/vcn/IVcnStatusCallback.aidl
similarity index 60%
copy from core/java/android/app/ILocalWallpaperColorConsumer.aidl
copy to core/java/android/net/vcn/IVcnStatusCallback.aidl
index 28b11ec..a7386718 100644
--- a/core/java/android/app/ILocalWallpaperColorConsumer.aidl
+++ b/core/java/android/net/vcn/IVcnStatusCallback.aidl
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 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
+ * 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,
@@ -14,14 +14,9 @@
* limitations under the License.
*/
-package android.app;
+package android.net.vcn;
-import android.app.WallpaperColors;
-import android.graphics.RectF;
-
-/**
- * @hide
- */
-oneway interface ILocalWallpaperColorConsumer {
- void onColorsChanged(in RectF area, in WallpaperColors colors);
+/** @hide */
+interface IVcnStatusCallback {
+ void onEnteredSafeMode();
}
\ No newline at end of file
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index 5eb4ba6..52cc218 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.net.NetworkRequest;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
@@ -41,7 +42,6 @@
* brought up on demand based on active {@link NetworkRequest}(s).
*
* @see VcnManager for more information on the Virtual Carrier Network feature
- * @hide
*/
public final class VcnConfig implements Parcelable {
@NonNull private static final String TAG = VcnConfig.class.getSimpleName();
@@ -56,7 +56,8 @@
@NonNull String packageName,
@NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs) {
mPackageName = packageName;
- mGatewayConnectionConfigs = Collections.unmodifiableSet(gatewayConnectionConfigs);
+ mGatewayConnectionConfigs =
+ Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
validate();
}
@@ -96,11 +97,7 @@
return mPackageName;
}
- /**
- * Retrieves the set of configured tunnels.
- *
- * @hide
- */
+ /** Retrieves the set of configured GatewayConnection(s). */
@NonNull
public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() {
return Collections.unmodifiableSet(mGatewayConnectionConfigs);
@@ -168,11 +165,7 @@
}
};
- /**
- * This class is used to incrementally build {@link VcnConfig} objects.
- *
- * @hide
- */
+ /** This class is used to incrementally build {@link VcnConfig} objects. */
public static final class Builder {
@NonNull private final String mPackageName;
@@ -190,7 +183,6 @@
*
* @param gatewayConnectionConfig the configuration for an individual gateway connection
* @return this {@link Builder} instance, for chaining
- * @hide
*/
@NonNull
public Builder addGatewayConnectionConfig(
@@ -205,7 +197,6 @@
* Builds and validates the VcnConfig.
*
* @return an immutable VcnConfig instance
- * @hide
*/
@NonNull
public VcnConfig build() {
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index cead2f1..40aa518 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -21,6 +21,8 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.PersistableBundle;
import android.util.ArraySet;
@@ -55,28 +57,23 @@
* subscription group under which this configuration is registered (see {@link
* VcnManager#setVcnConfig}).
*
- * <p>Services that can be provided by a VCN network, or required for underlying networks are
- * limited to services provided by cellular networks:
+ * <p>As an abstraction of a cellular network, services that can be provided by a VCN network, or
+ * required for underlying networks are limited to services provided by cellular networks:
*
* <ul>
- * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MMS}
- * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_SUPL}
- * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_DUN}
- * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_FOTA}
- * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IMS}
- * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_CBS}
- * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_IA}
- * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_RCS}
- * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_XCAP}
- * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_EIMS}
- * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET}
- * <li>{@link android.net.NetworkCapabilities.NET_CAPABILITY_MCX}
+ * <li>{@link NetworkCapabilities#NET_CAPABILITY_MMS}
+ * <li>{@link NetworkCapabilities#NET_CAPABILITY_SUPL}
+ * <li>{@link NetworkCapabilities#NET_CAPABILITY_DUN}
+ * <li>{@link NetworkCapabilities#NET_CAPABILITY_FOTA}
+ * <li>{@link NetworkCapabilities#NET_CAPABILITY_IMS}
+ * <li>{@link NetworkCapabilities#NET_CAPABILITY_CBS}
+ * <li>{@link NetworkCapabilities#NET_CAPABILITY_IA}
+ * <li>{@link NetworkCapabilities#NET_CAPABILITY_RCS}
+ * <li>{@link NetworkCapabilities#NET_CAPABILITY_XCAP}
+ * <li>{@link NetworkCapabilities#NET_CAPABILITY_EIMS}
+ * <li>{@link NetworkCapabilities#NET_CAPABILITY_INTERNET}
+ * <li>{@link NetworkCapabilities#NET_CAPABILITY_MCX}
* </ul>
- *
- * <p>The meteredness and roaming of the VCN {@link Network} will be determined by that of the
- * underlying Network(s).
- *
- * @hide
*/
public final class VcnGatewayConnectionConfig {
// TODO: Use MIN_MTU_V6 once it is public, @hide
@@ -249,8 +246,7 @@
* ascending numerical order.
*
* @see Builder#addExposedCapability(int)
- * @see Builder#clearExposedCapability(int)
- * @hide
+ * @see Builder#removeExposedCapability(int)
*/
@NonNull
public int[] getExposedCapabilities() {
@@ -278,8 +274,7 @@
* <p>The returned integer-value capabilities will be sorted in ascending numerical order.
*
* @see Builder#addRequiredUnderlyingCapability(int)
- * @see Builder#clearRequiredUnderlyingCapability(int)
- * @hide
+ * @see Builder#removeRequiredUnderlyingCapability(int)
*/
@NonNull
public int[] getRequiredUnderlyingCapabilities() {
@@ -305,7 +300,6 @@
* Retrieves the configured retry intervals.
*
* @see Builder#setRetryInterval(long[])
- * @hide
*/
@NonNull
public long[] getRetryInterval() {
@@ -317,7 +311,7 @@
*
* <p>Left to prevent the need to make major changes while changes are actively in flight.
*
- * @deprecated use getRequiredUnderlyingCapabilities() instead
+ * @deprecated use getRetryInterval() instead
* @hide
*/
@Deprecated
@@ -329,8 +323,7 @@
/**
* Retrieves the maximum MTU allowed for this Gateway Connection.
*
- * @see Builder.setMaxMtu(int)
- * @hide
+ * @see Builder#setMaxMtu(int)
*/
@IntRange(from = MIN_MTU_V6)
public int getMaxMtu() {
@@ -388,8 +381,6 @@
/**
* This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects.
- *
- * @hide
*/
public static final class Builder {
@NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
@@ -409,7 +400,6 @@
* @return this {@link Builder} instance, for chaining
* @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
* Connection
- * @hide
*/
@NonNull
public Builder addExposedCapability(@VcnSupportedCapability int exposedCapability) {
@@ -427,10 +417,10 @@
* @return this {@link Builder} instance, for chaining
* @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
* Connection
- * @hide
*/
@NonNull
- public Builder clearExposedCapability(@VcnSupportedCapability int exposedCapability) {
+ @SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap
+ public Builder removeExposedCapability(@VcnSupportedCapability int exposedCapability) {
checkValidCapability(exposedCapability);
mExposedCapabilities.remove(exposedCapability);
@@ -445,7 +435,6 @@
* @return this {@link Builder} instance, for chaining
* @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
* networks
- * @hide
*/
@NonNull
public Builder addRequiredUnderlyingCapability(
@@ -468,10 +457,10 @@
* @return this {@link Builder} instance, for chaining
* @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
* networks
- * @hide
*/
@NonNull
- public Builder clearRequiredUnderlyingCapability(
+ @SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap
+ public Builder removeRequiredUnderlyingCapability(
@VcnSupportedCapability int underlyingCapability) {
checkValidCapability(underlyingCapability);
@@ -501,7 +490,6 @@
* 15m]}
* @return this {@link Builder} instance, for chaining
* @see VcnManager for additional discussion on fail-safe mode
- * @hide
*/
@NonNull
public Builder setRetryInterval(@NonNull long[] retryIntervalsMs) {
@@ -523,7 +511,6 @@
* @param maxMtu the maximum MTU allowed for this Gateway Connection. Must be greater than
* the IPv6 minimum MTU of 1280. Defaults to 1500.
* @return this {@link Builder} instance, for chaining
- * @hide
*/
@NonNull
public Builder setMaxMtu(@IntRange(from = MIN_MTU_V6) int maxMtu) {
@@ -538,7 +525,6 @@
* Builds and validates the VcnGatewayConnectionConfig.
*
* @return an immutable VcnGatewayConnectionConfig instance
- * @hide
*/
@NonNull
public VcnGatewayConnectionConfig build() {
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 1a38338..aed64de 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
+import android.os.Binder;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -39,12 +40,12 @@
/**
* VcnManager publishes APIs for applications to configure and manage Virtual Carrier Networks.
*
- * <p>A VCN creates a virtualization layer to allow MVNOs to aggregate heterogeneous physical
+ * <p>A VCN creates a virtualization layer to allow carriers to aggregate heterogeneous physical
* networks, unifying them as a single carrier network. This enables infrastructure flexibility on
- * the part of MVNOs without impacting user connectivity, abstracting the physical network
+ * the part of carriers without impacting user connectivity, abstracting the physical network
* technologies as an implementation detail of their public network.
*
- * <p>Each VCN virtualizes an Carrier's network by building tunnels to a carrier's core network over
+ * <p>Each VCN virtualizes a carrier's network by building tunnels to a carrier's core network over
* carrier-managed physical links and supports a IP mobility layer to ensure seamless transitions
* between the underlying networks. Each VCN is configured based on a Subscription Group (see {@link
* android.telephony.SubscriptionManager}) and aggregates all networks that are brought up based on
@@ -62,8 +63,6 @@
* tasks. In Safe Mode, the system will allow underlying cellular networks to be used as default.
* Additionally, during Safe Mode, the VCN will continue to retry the connections, and will
* automatically exit Safe Mode if all active tunnels connect successfully.
- *
- * @hide
*/
@SystemService(Context.VCN_MANAGEMENT_SERVICE)
public class VcnManager {
@@ -101,7 +100,6 @@
return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS);
}
- // TODO: Make setVcnConfig(), clearVcnConfig() Public API
/**
* Sets the VCN configuration for a given subscription group.
*
@@ -113,11 +111,10 @@
*
* @param subscriptionGroup the subscription group that the configuration should be applied to
* @param config the configuration parameters for the VCN
- * @throws SecurityException if the caller does not have carrier privileges, or is not running
- * as the primary user
- * @throws IOException if the configuration failed to be persisted. A caller encountering this
- * exception should attempt to retry (possibly after a delay).
- * @hide
+ * @throws SecurityException if the caller does not have carrier privileges for the provided
+ * subscriptionGroup, or is not running as the primary user
+ * @throws IOException if the configuration failed to be saved and persisted to disk. This may
+ * occur due to temporary disk errors, or more permanent conditions such as a full disk.
*/
@RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant
public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config)
@@ -134,7 +131,6 @@
}
}
- // TODO: Make setVcnConfig(), clearVcnConfig() Public API
/**
* Clears the VCN configuration for a given subscription group.
*
@@ -145,9 +141,8 @@
* @param subscriptionGroup the subscription group that the configuration should be applied to
* @throws SecurityException if the caller does not have carrier privileges, or is not running
* as the primary user
- * @throws IOException if the configuration failed to be cleared. A caller encountering this
- * exception should attempt to retry (possibly after a delay).
- * @hide
+ * @throws IOException if the configuration failed to be cleared from disk. This may occur due
+ * to temporary disk errors, or more permanent conditions such as a full disk.
*/
@RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant
public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) throws IOException {
@@ -267,6 +262,100 @@
}
}
+ // TODO: make VcnStatusCallback @SystemApi
+ /**
+ * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs.
+ *
+ * <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a
+ * subscription group.
+ *
+ * @hide
+ */
+ public abstract static class VcnStatusCallback {
+ private VcnStatusCallbackBinder mCbBinder;
+
+ /**
+ * Invoked when the VCN for this Callback's subscription group enters safe mode.
+ *
+ * <p>A VCN will be put into safe mode if any of the gateway connections were unable to
+ * establish a connection within a system-determined timeout (while underlying networks were
+ * available).
+ *
+ * <p>A VCN-configuring app may opt to exit safe mode by (re)setting the VCN configuration
+ * via {@link #setVcnConfig(ParcelUuid, VcnConfig)}.
+ */
+ public abstract void onEnteredSafeMode();
+ }
+
+ /**
+ * Registers the given callback to receive status updates for the specified subscription.
+ *
+ * <p>Callbacks can be registered for a subscription before {@link VcnConfig}s are set for it.
+ *
+ * <p>A {@link VcnStatusCallback} may only be registered for one subscription at a time. {@link
+ * VcnStatusCallback}s may be reused once unregistered.
+ *
+ * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier
+ * privileges for the specified subscription at the time of invocation.
+ *
+ * @param subscriptionGroup The subscription group to match for callbacks
+ * @param executor The {@link Executor} to be used for invoking callbacks
+ * @param callback The VcnStatusCallback to be registered
+ * @throws IllegalStateException if callback is currently registered with VcnManager
+ * @hide
+ */
+ public void registerVcnStatusCallback(
+ @NonNull ParcelUuid subscriptionGroup,
+ @NonNull Executor executor,
+ @NonNull VcnStatusCallback callback) {
+ requireNonNull(subscriptionGroup, "subscriptionGroup must not be null");
+ requireNonNull(executor, "executor must not be null");
+ requireNonNull(callback, "callback must not be null");
+
+ synchronized (callback) {
+ if (callback.mCbBinder != null) {
+ throw new IllegalStateException("callback is already registered with VcnManager");
+ }
+ callback.mCbBinder = new VcnStatusCallbackBinder(executor, callback);
+
+ try {
+ mService.registerVcnStatusCallback(
+ subscriptionGroup, callback.mCbBinder, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ callback.mCbBinder = null;
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Unregisters the given callback.
+ *
+ * <p>Once unregistered, the callback will stop receiving status updates for the subscription it
+ * was registered with.
+ *
+ * @param callback The callback to be unregistered
+ * @hide
+ */
+ public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) {
+ requireNonNull(callback, "callback must not be null");
+
+ synchronized (callback) {
+ if (callback.mCbBinder == null) {
+ // no Binder attached to this callback, so it's not currently registered
+ return;
+ }
+
+ try {
+ mService.unregisterVcnStatusCallback(callback.mCbBinder);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ callback.mCbBinder = null;
+ }
+ }
+ }
+
/**
* Binder wrapper for added VcnUnderlyingNetworkPolicyListeners to receive signals from System
* Server.
@@ -286,7 +375,30 @@
@Override
public void onPolicyChanged() {
- mExecutor.execute(() -> mListener.onPolicyChanged());
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> mListener.onPolicyChanged()));
+ }
+ }
+
+ /**
+ * Binder wrapper for VcnStatusCallbacks to receive signals from VcnManagementService.
+ *
+ * @hide
+ */
+ private class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub {
+ @NonNull private final Executor mExecutor;
+ @NonNull private final VcnStatusCallback mCallback;
+
+ private VcnStatusCallbackBinder(
+ @NonNull Executor executor, @NonNull VcnStatusCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onEnteredSafeMode() {
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> mCallback.onEnteredSafeMode()));
}
}
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index b39c182..7437e037 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -113,6 +113,7 @@
boolean hasBadge(int userId);
boolean isUserUnlocked(int userId);
boolean isUserRunning(int userId);
+ boolean isUserForeground();
boolean isUserNameSet(int userId);
boolean hasRestrictedProfiles();
boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ea1ce37..8bdfd3d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2300,6 +2300,19 @@
}
/**
+ * Checks if the calling user is running on foreground.
+ *
+ * @return whether the calling user is running on foreground.
+ */
+ public boolean isUserForeground() {
+ try {
+ return mService.isUserForeground();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return whether the calling user is running in an "unlocked" state.
* <p>
* On devices with direct boot, a user is unlocked only after they've
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/core/java/android/permission/AdminPermissionControlParams.aidl
similarity index 60%
copy from core/java/android/app/ILocalWallpaperColorConsumer.aidl
copy to core/java/android/permission/AdminPermissionControlParams.aidl
index 28b11ec..35e63d4 100644
--- a/core/java/android/app/ILocalWallpaperColorConsumer.aidl
+++ b/core/java/android/permission/AdminPermissionControlParams.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
+/**
+ * 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
+ * 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,
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-package android.app;
+package android.permission;
-import android.app.WallpaperColors;
-import android.graphics.RectF;
-
-/**
- * @hide
- */
-oneway interface ILocalWallpaperColorConsumer {
- void onColorsChanged(in RectF area, in WallpaperColors colors);
-}
\ No newline at end of file
+parcelable AdminPermissionControlParams;
diff --git a/core/java/android/permission/AdminPermissionControlParams.java b/core/java/android/permission/AdminPermissionControlParams.java
new file mode 100644
index 0000000..49507220
--- /dev/null
+++ b/core/java/android/permission/AdminPermissionControlParams.java
@@ -0,0 +1,132 @@
+/*
+ * 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.permission;
+
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.admin.DevicePolicyManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A data object representing an admin's request to control a certain permission
+ * for a certain app.
+ * This class is processed by the Permission Controller's
+ * setRuntimePermissionGrantStateByDeviceAdmin method.
+ *
+ * @hide
+ */
+@SystemApi
+public final class AdminPermissionControlParams implements Parcelable {
+ // The package to grant/deny the permission to.
+ private final @NonNull String mGranteePackageName;
+ // The permission to grant/deny.
+ private final @NonNull String mPermission;
+ // The grant state (granted/denied/default).
+ private final @DevicePolicyManager.PermissionGrantState int mGrantState;
+ // Whether the admin can grant sensors-related permissions.
+ private final boolean mCanAdminGrantSensorsPermissions;
+
+ /**
+ * @hide
+ * A new instance is only created by the framework, so the constructor need not be visible
+ * as system API.
+ */
+ public AdminPermissionControlParams(@NonNull String granteePackageName,
+ @NonNull String permission,
+ int grantState, boolean canAdminGrantSensorsPermissions) {
+ Preconditions.checkStringNotEmpty(granteePackageName, "Package name must not be empty.");
+ Preconditions.checkStringNotEmpty(permission, "Permission must not be empty.");
+ checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED
+ || grantState == PERMISSION_GRANT_STATE_DENIED
+ || grantState == PERMISSION_GRANT_STATE_DEFAULT);
+
+ mGranteePackageName = granteePackageName;
+ mPermission = permission;
+ mGrantState = grantState;
+ mCanAdminGrantSensorsPermissions = canAdminGrantSensorsPermissions;
+ }
+
+ public static final @NonNull Creator<AdminPermissionControlParams> CREATOR =
+ new Creator<AdminPermissionControlParams>() {
+ @Override
+ public AdminPermissionControlParams createFromParcel(Parcel in) {
+ String granteePackageName = in.readString();
+ String permission = in.readString();
+ int grantState = in.readInt();
+ boolean mayAdminGrantSensorPermissions = in.readBoolean();
+
+ return new AdminPermissionControlParams(granteePackageName, permission,
+ grantState, mayAdminGrantSensorPermissions);
+ }
+
+ @Override
+ public AdminPermissionControlParams[] newArray(int size) {
+ return new AdminPermissionControlParams[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mGranteePackageName);
+ dest.writeString(mPermission);
+ dest.writeInt(mGrantState);
+ dest.writeBoolean(mCanAdminGrantSensorsPermissions);
+ }
+
+ /** Returns the name of the package the permission applies to */
+ public @NonNull String getGranteePackageName() {
+ return mGranteePackageName;
+ }
+
+ /** Returns the permission name */
+ public @NonNull String getPermission() {
+ return mPermission;
+ }
+
+ /** Returns the grant state */
+ public int getGrantState() {
+ return mGrantState;
+ }
+
+ /**
+ * return true if the admin may control grants of permissions related to sensors.
+ */
+ public boolean canAdminGrantSensorsPermissions() {
+ return mCanAdminGrantSensorsPermissions;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "Grantee %s Permission %s state: %d admin grant of sensors permissions: %b",
+ mGranteePackageName, mPermission, mGrantState, mCanAdminGrantSensorsPermissions);
+ }
+}
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 084cc2f..6d677f3 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -20,6 +20,7 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
+import android.permission.AdminPermissionControlParams;
import com.android.internal.infra.AndroidFuture;
/**
@@ -39,8 +40,8 @@
void countPermissionApps(in List<String> permissionNames, int flags,
in AndroidFuture callback);
void getPermissionUsages(boolean countSystem, long numMillis, in AndroidFuture callback);
- void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, String packageName,
- String permission, int grantState, in AndroidFuture callback);
+ void setRuntimePermissionGrantStateByDeviceAdminFromParams(String callerPackageName,
+ in AdminPermissionControlParams params, in AndroidFuture callback);
void grantOrUpgradeDefaultRuntimePermissions(in AndroidFuture callback);
void notifyOneTimePermissionSessionTimeout(String packageName);
void updateUserSensitiveForApp(int uid, in AndroidFuture callback);
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index f306805..084b18e 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -16,13 +16,9 @@
package android.permission;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
import static android.permission.PermissionControllerService.SERVICE_INTERFACE;
import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
-import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkFlagsArgument;
@@ -39,7 +35,6 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.ActivityThread;
-import android.app.admin.DevicePolicyManager.PermissionGrantState;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -70,6 +65,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -323,11 +319,11 @@
/**
* Set the runtime permission state from a device admin.
+ * This variant takes into account whether the admin may or may not grant sensors-related
+ * permissions.
*
* @param callerPackageName The package name of the admin requesting the change
- * @param packageName Package the permission belongs to
- * @param permission Permission to change
- * @param grantState State to set the permission into
+ * @param params Information about the permission being granted.
* @param executor Executor to run the {@code callback} on
* @param callback The callback
*
@@ -338,30 +334,27 @@
Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY},
conditional = true)
public void setRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName,
- @NonNull String packageName, @NonNull String permission,
- @PermissionGrantState int grantState, @NonNull @CallbackExecutor Executor executor,
+ @NonNull AdminPermissionControlParams params,
+ @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<Boolean> callback) {
checkStringNotEmpty(callerPackageName);
- checkStringNotEmpty(packageName);
- checkStringNotEmpty(permission);
- checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED
- || grantState == PERMISSION_GRANT_STATE_DENIED
- || grantState == PERMISSION_GRANT_STATE_DEFAULT);
- checkNotNull(executor);
- checkNotNull(callback);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ Objects.requireNonNull(params, "Admin control params must not be null.");
mRemoteService.postAsync(service -> {
AndroidFuture<Boolean> setRuntimePermissionGrantStateResult = new AndroidFuture<>();
- service.setRuntimePermissionGrantStateByDeviceAdmin(
- callerPackageName, packageName, permission, grantState,
+ service.setRuntimePermissionGrantStateByDeviceAdminFromParams(
+ callerPackageName, params,
setRuntimePermissionGrantStateResult);
return setRuntimePermissionGrantStateResult;
}).whenCompleteAsync((setRuntimePermissionGrantStateResult, err) -> {
final long token = Binder.clearCallingIdentity();
try {
if (err != null) {
- Log.e(TAG, "Error setting permissions state for device admin " + packageName,
- err);
+ Log.e(TAG,
+ "Error setting permissions state for device admin "
+ + callerPackageName, err);
callback.accept(false);
} else {
callback.accept(Boolean.TRUE.equals(setRuntimePermissionGrantStateResult));
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 8105b65..ad9e8b3 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -16,9 +16,7 @@
package android.permission;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
-import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
import static android.permission.PermissionControllerManager.COUNT_ONLY_WHEN_GRANTED;
import static android.permission.PermissionControllerManager.COUNT_WHEN_SYSTEM;
@@ -259,6 +257,8 @@
}
/**
+ * @deprecated See {@link #onSetRuntimePermissionGrantStateByDeviceAdmin(String,
+ * AdminPermissionControlParams, Consumer)}.
* Set the runtime permission state from a device admin.
*
* @param callerPackageName The package name of the admin requesting the change
@@ -267,6 +267,7 @@
* @param grantState State to set the permission into
* @param callback Callback waiting for whether the state could be set or not
*/
+ @Deprecated
@BinderThread
public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(
@NonNull String callerPackageName, @NonNull String packageName,
@@ -274,6 +275,20 @@
@NonNull Consumer<Boolean> callback);
/**
+ * Set the runtime permission state from a device admin.
+ *
+ * @param callerPackageName The package name of the admin requesting the change
+ * @param params Parameters of admin request.
+ * @param callback Callback waiting for whether the state could be set or not
+ */
+ @BinderThread
+ public void onSetRuntimePermissionGrantStateByDeviceAdmin(
+ @NonNull String callerPackageName, @NonNull AdminPermissionControlParams params,
+ @NonNull Consumer<Boolean> callback) {
+ throw new AbstractMethodError("Must be overridden in implementing class");
+ }
+
+ /**
* Called when a package is considered inactive based on the criteria given by
* {@link PermissionManager#startOneTimePermissionSession(String, long, int, int)}.
* This method is called at the end of a one-time permission session
@@ -468,32 +483,26 @@
}
@Override
- public void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName,
- String packageName, String permission, int grantState,
+ public void setRuntimePermissionGrantStateByDeviceAdminFromParams(
+ String callerPackageName, AdminPermissionControlParams params,
AndroidFuture callback) {
checkStringNotEmpty(callerPackageName);
- checkStringNotEmpty(packageName);
- checkStringNotEmpty(permission);
- checkArgument(grantState == PERMISSION_GRANT_STATE_GRANTED
- || grantState == PERMISSION_GRANT_STATE_DENIED
- || grantState == PERMISSION_GRANT_STATE_DEFAULT);
- checkNotNull(callback);
-
- if (grantState == PERMISSION_GRANT_STATE_DENIED) {
+ if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) {
enforceSomePermissionsGrantedToCaller(
Manifest.permission.GRANT_RUNTIME_PERMISSIONS);
}
- if (grantState == PERMISSION_GRANT_STATE_DENIED) {
+ if (params.getGrantState() == PERMISSION_GRANT_STATE_DENIED) {
enforceSomePermissionsGrantedToCaller(
Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
}
enforceSomePermissionsGrantedToCaller(
Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
+ checkNotNull(callback);
onSetRuntimePermissionGrantStateByDeviceAdmin(callerPackageName,
- packageName, permission, grantState, callback::complete);
+ params, callback::complete);
}
@Override
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 91e091c..e134c29 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -525,6 +525,13 @@
*/
public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
+ /**
+ * Namespace for game overlay related features.
+ *
+ * @hide
+ */
+ public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f02e532..e979e13 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1902,6 +1902,18 @@
public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID";
/**
+ * Activity Extra: An {@code Arraylist<String>} of {@link NotificationChannel} field names to
+ * show on the Settings UI.
+ *
+ * <p>
+ * This is an optional extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}. If
+ * included the system will filter out any Settings that doesn't appear in this list that
+ * otherwise would display.
+ */
+ public static final String EXTRA_CHANNEL_FILTER_LIST
+ = "android.provider.extra.CHANNEL_FILTER_LIST";
+
+ /**
* Activity Action: Show notification redaction settings.
*
* @hide
diff --git a/core/java/android/provider/SimPhonebookContract.java b/core/java/android/provider/SimPhonebookContract.java
index 2efc212..074d5f1 100644
--- a/core/java/android/provider/SimPhonebookContract.java
+++ b/core/java/android/provider/SimPhonebookContract.java
@@ -29,11 +29,8 @@
import android.annotation.WorkerThread;
import android.content.ContentResolver;
import android.content.ContentValues;
-import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
@@ -63,7 +60,6 @@
*
* @hide
*/
- @SystemApi
public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";
private SimPhonebookContract() {
@@ -76,7 +72,6 @@
* @hide
*/
@NonNull
- @SystemApi
public static String getEfUriPath(@ElementaryFiles.EfType int efType) {
switch (efType) {
case EF_ADN:
@@ -122,12 +117,12 @@
* The name for this record.
*
* <p>An {@link IllegalArgumentException} will be thrown by insert and update if this
- * exceeds the maximum supported length or contains unsupported characters.
- * {@link #validateName(ContentResolver, int, int, String)} )} can be used to
- * check whether the name is supported.
+ * exceeds the maximum supported length. Use
+ * {@link #getEncodedNameLength(ContentResolver, String)} to check how long the name
+ * will be after encoding.
*
* @see ElementaryFiles#NAME_MAX_LENGTH
- * @see #validateName(ContentResolver, int, int, String) )
+ * @see #getEncodedNameLength(ContentResolver, String)
*/
public static final String NAME = "name";
/**
@@ -149,24 +144,31 @@
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2";
/**
- * The path segment that is appended to {@link #getContentUri(int, int)} which indicates
- * that the following path segment contains a name to be validated.
- *
- * @hide
- * @see #validateName(ContentResolver, int, int, String)
+ * Value returned from {@link #getEncodedNameLength(ContentResolver, String)} when the name
+ * length could not be determined because the name could not be encoded.
*/
- @SystemApi
- public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name";
+ public static final int ERROR_NAME_UNSUPPORTED = -1;
/**
- * The key for a cursor extra that contains the result of a validate name query.
+ * The method name used to get the encoded length of a value for {@link SimRecords#NAME}
+ * column.
*
* @hide
- * @see #validateName(ContentResolver, int, int, String)
+ * @see #getEncodedNameLength(ContentResolver, String)
+ * @see ContentResolver#call(String, String, String, Bundle)
*/
- @SystemApi
- public static final String EXTRA_NAME_VALIDATION_RESULT =
- "android.provider.extra.NAME_VALIDATION_RESULT";
+ public static final String GET_ENCODED_NAME_LENGTH_METHOD_NAME = "get_encoded_name_length";
+
+ /**
+ * Extra key used for an integer value that contains the length in bytes of an encoded
+ * name.
+ *
+ * @hide
+ * @see #getEncodedNameLength(ContentResolver, String)
+ * @see #GET_ENCODED_NAME_LENGTH_METHOD_NAME
+ */
+ public static final String EXTRA_ENCODED_NAME_LENGTH =
+ "android.provider.extra.ENCODED_NAME_LENGTH";
/**
@@ -244,32 +246,34 @@
}
/**
- * Validates a value that is being provided for the {@link #NAME} column.
+ * Returns the number of bytes required to encode the specified name when it is stored
+ * on the SIM.
*
- * <p>The return value can be used to check if the name is valid. If it is not valid then
- * inserts and updates to the specified elementary file that use the provided name value
- * will throw an {@link IllegalArgumentException}.
+ * <p>{@link ElementaryFiles#NAME_MAX_LENGTH} is specified in bytes but the encoded name
+ * may require more than 1 byte per character depending on the characters it contains. So
+ * this method can be used to check whether a name exceeds the max length.
*
- * <p>If the specified SIM or elementary file don't exist then
- * {@link NameValidationResult#getMaxEncodedLength()} will be zero and
- * {@link NameValidationResult#isValid()} will return false.
+ * @return the number of bytes required by the encoded name or
+ * {@link #ERROR_NAME_UNSUPPORTED} if the name could not be encoded.
+ * @throws IllegalStateException if the provider fails to return the length.
+ * @see SimRecords#NAME
+ * @see ElementaryFiles#NAME_MAX_LENGTH
*/
- @NonNull
@WorkerThread
- public static NameValidationResult validateName(
- @NonNull ContentResolver resolver, int subscriptionId,
- @ElementaryFiles.EfType int efType,
- @NonNull String name) {
- Bundle queryArgs = new Bundle();
- queryArgs.putString(SimRecords.NAME, name);
- try (Cursor cursor =
- resolver.query(buildContentUri(subscriptionId, efType)
- .appendPath(VALIDATE_NAME_PATH_SEGMENT)
- .build(), null, queryArgs, null)) {
- NameValidationResult result = cursor.getExtras()
- .getParcelable(EXTRA_NAME_VALIDATION_RESULT);
- return result != null ? result : new NameValidationResult(name, "", 0, 0);
+ public static int getEncodedNameLength(
+ @NonNull ContentResolver resolver, @NonNull String name) {
+ Objects.requireNonNull(name);
+ Bundle result = resolver.call(AUTHORITY, GET_ENCODED_NAME_LENGTH_METHOD_NAME, name,
+ null);
+ if (result == null || !result.containsKey(EXTRA_ENCODED_NAME_LENGTH)) {
+ throw new IllegalStateException("Provider malfunction: no length was returned.");
}
+ int length = result.getInt(EXTRA_ENCODED_NAME_LENGTH, ERROR_NAME_UNSUPPORTED);
+ if (length < 0 && length != ERROR_NAME_UNSUPPORTED) {
+ throw new IllegalStateException(
+ "Provider malfunction: invalid length was returned.");
+ }
+ return length;
}
private static Uri.Builder buildContentUri(
@@ -281,106 +285,6 @@
.appendPath(getEfUriPath(efType));
}
- /** Contains details about the validity of a value provided for the {@link #NAME} column. */
- public static final class NameValidationResult implements Parcelable {
-
- @NonNull
- public static final Creator<NameValidationResult> CREATOR =
- new Creator<NameValidationResult>() {
-
- @Override
- public NameValidationResult createFromParcel(@NonNull Parcel in) {
- return new NameValidationResult(in);
- }
-
- @NonNull
- @Override
- public NameValidationResult[] newArray(int size) {
- return new NameValidationResult[size];
- }
- };
-
- private final String mName;
- private final String mSanitizedName;
- private final int mEncodedLength;
- private final int mMaxEncodedLength;
-
- /** Creates a new instance from the provided values. */
- public NameValidationResult(@NonNull String name, @NonNull String sanitizedName,
- int encodedLength, int maxEncodedLength) {
- this.mName = Objects.requireNonNull(name);
- this.mSanitizedName = Objects.requireNonNull(sanitizedName);
- this.mEncodedLength = encodedLength;
- this.mMaxEncodedLength = maxEncodedLength;
- }
-
- private NameValidationResult(Parcel in) {
- this(in.readString(), in.readString(), in.readInt(), in.readInt());
- }
-
- /** Returns the original name that is being validated. */
- @NonNull
- public String getName() {
- return mName;
- }
-
- /**
- * Returns a sanitized copy of the original name with all unsupported characters
- * replaced with spaces.
- */
- @NonNull
- public String getSanitizedName() {
- return mSanitizedName;
- }
-
- /**
- * Returns whether the original name isValid.
- *
- * <p>If this returns false then inserts and updates using the name will throw an
- * {@link IllegalArgumentException}
- */
- public boolean isValid() {
- return mMaxEncodedLength > 0 && mEncodedLength <= mMaxEncodedLength
- && Objects.equals(
- mName, mSanitizedName);
- }
-
- /** Returns whether the character at the specified position is supported by the SIM. */
- public boolean isSupportedCharacter(int position) {
- return mName.charAt(position) == mSanitizedName.charAt(position);
- }
-
- /**
- * Returns the number of bytes required to save the name.
- *
- * <p>This may be more than the number of characters in the name.
- */
- public int getEncodedLength() {
- return mEncodedLength;
- }
-
- /**
- * Returns the maximum number of bytes that are supported for the name.
- *
- * @see ElementaryFiles#NAME_MAX_LENGTH
- */
- public int getMaxEncodedLength() {
- return mMaxEncodedLength;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(mName);
- dest.writeString(mSanitizedName);
- dest.writeInt(mEncodedLength);
- dest.writeInt(mMaxEncodedLength);
- }
- }
}
/** Constants for metadata about the elementary files of the SIM cards in the phone. */
@@ -446,13 +350,10 @@
*/
public static final int EF_SDN = 3;
/** @hide */
- @SystemApi
public static final String EF_ADN_PATH_SEGMENT = "adn";
/** @hide */
- @SystemApi
public static final String EF_FDN_PATH_SEGMENT = "fdn";
/** @hide */
- @SystemApi
public static final String EF_SDN_PATH_SEGMENT = "sdn";
/** The MIME type of CONTENT_URI providing a directory of ADN-like elementary files. */
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file";
@@ -464,7 +365,6 @@
*
* @hide
*/
- @SystemApi
public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files";
/** Content URI for the ADN-like elementary files available on the device. */
@@ -480,8 +380,7 @@
* Returns a content uri for a specific elementary file.
*
* <p>If a SIM with the specified subscriptionId is not present an exception will be thrown.
- * If the SIM doesn't support the specified elementary file it will have a zero value for
- * {@link #MAX_RECORDS}.
+ * If the SIM doesn't support the specified elementary file it will return an empty cursor.
*/
@NonNull
public static Uri getItemUri(int subscriptionId, @EfType int efType) {
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index a79b197..5a89cdf 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -188,6 +188,7 @@
public static final int KM_PURPOSE_VERIFY = KeyPurpose.VERIFY;
public static final int KM_PURPOSE_WRAP = KeyPurpose.WRAP_KEY;
public static final int KM_PURPOSE_AGREE_KEY = KeyPurpose.AGREE_KEY;
+ public static final int KM_PURPOSE_ATTEST_KEY = KeyPurpose.ATTEST_KEY;
// Key formats.
public static final int KM_KEY_FORMAT_X509 = KeyFormat.X509;
diff --git a/core/java/android/service/wallpaper/EngineWindowPage.java b/core/java/android/service/wallpaper/EngineWindowPage.java
deleted file mode 100644
index 9d2e5b9..0000000
--- a/core/java/android/service/wallpaper/EngineWindowPage.java
+++ /dev/null
@@ -1,96 +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 android.service.wallpaper;
-
-import android.app.WallpaperColors;
-import android.graphics.Bitmap;
-import android.graphics.RectF;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
-
-/**
- * This class represents a page of a launcher page used by the wallpaper
- * @hide
- */
-public class EngineWindowPage {
- private Bitmap mScreenShot;
- private volatile long mLastUpdateTime;
- private Set<RectF> mCallbackAreas = new ArraySet<>();
- private Map<RectF, WallpaperColors> mRectFColors = new ArrayMap<>();
-
- /** should be locked extrnally */
- public void addArea(RectF area) {
- mCallbackAreas.add(area);
- }
-
- /** should be locked extrnally */
- public void addWallpaperColors(RectF area, WallpaperColors colors) {
- mCallbackAreas.add(area);
- mRectFColors.put(area, colors);
- }
-
- /** get screenshot bitmap */
- public Bitmap getBitmap() {
- if (mScreenShot == null || mScreenShot.isRecycled()) return null;
- return mScreenShot;
- }
-
- /** remove callbacks for an area */
- public void removeArea(RectF area) {
- mCallbackAreas.remove(area);
- mRectFColors.remove(area);
- }
-
- /** set the last time the screenshot was updated */
- public void setLastUpdateTime(long lastUpdateTime) {
- mLastUpdateTime = lastUpdateTime;
- }
-
- /** get last screenshot time */
- public long getLastUpdateTime() {
- return mLastUpdateTime;
- }
-
- /** get colors for an area */
- public WallpaperColors getColors(RectF rect) {
- return mRectFColors.get(rect);
- }
-
- /** set the new bitmap version */
- public void setBitmap(Bitmap screenShot) {
- mScreenShot = screenShot;
- }
-
- /** get areas of interest */
- public Set<RectF> getAreas() {
- return mCallbackAreas;
- }
-
- /** run operations on this page */
- public synchronized void execSync(Consumer<EngineWindowPage> run) {
- run.accept(this);
- }
-
- /** nullify the area color */
- public void removeColor(RectF colorArea) {
- mRectFColors.remove(colorArea);
- }
-}
diff --git a/core/java/android/service/wallpaper/IWallpaperConnection.aidl b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
index f81ed34..f334d9d 100644
--- a/core/java/android/service/wallpaper/IWallpaperConnection.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
@@ -16,7 +16,6 @@
package android.service.wallpaper;
-import android.graphics.RectF;
import android.os.ParcelFileDescriptor;
import android.service.wallpaper.IWallpaperEngine;
import android.app.WallpaperColors;
@@ -29,5 +28,4 @@
void engineShown(IWallpaperEngine engine);
ParcelFileDescriptor setWallpaper(String name);
void onWallpaperColorsChanged(in WallpaperColors colors, int displayId);
- void onLocalWallpaperColorsChanged(in RectF area, in WallpaperColors colors, int displayId);
}
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index fbb449d..90392e6 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -16,10 +16,7 @@
package android.service.wallpaper;
-import android.app.ILocalWallpaperColorConsumer;
-import android.app.WallpaperColors;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.view.MotionEvent;
import android.os.Bundle;
@@ -42,6 +39,4 @@
void destroy();
void setZoomOut(float scale);
void scalePreview(in Rect positionInWindow);
- void removeLocalColorsAreas(in List<RectF> regions);
- void addLocalColorsAreas(in List<RectF> regions);
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0b447d5..82e0b4a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -23,7 +23,6 @@
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import android.annotation.FloatRange;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -43,7 +42,6 @@
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
@@ -55,8 +53,6 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.Trace;
-import android.util.ArraySet;
import android.util.Log;
import android.util.MergedConfiguration;
import android.view.Display;
@@ -70,8 +66,6 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.MotionEvent;
-import android.view.PixelCopy;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.View;
@@ -89,10 +83,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
/**
@@ -124,12 +115,7 @@
public static final String SERVICE_META_DATA = "android.service.wallpaper";
static final String TAG = "WallpaperService";
- static final boolean DEBUG = true;
- static final float MIN_PAGE_ALLOWED_MARGIN = .05f;
- private static final int MIN_BITMAP_SCREENSHOT_WIDTH = 64;
- private static final long DEFAULT_UPDATE_SCREENSHOT_DURATION = 60 * 1000; //Once per minute
- private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
- new RectF(0, 0, 1, 1);
+ static final boolean DEBUG = false;
private static final int DO_ATTACH = 10;
private static final int DO_DETACH = 20;
@@ -148,8 +134,6 @@
private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
private static final int MSG_ZOOM = 10100;
private static final int MSG_SCALE_PREVIEW = 10110;
- private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
- Float.NEGATIVE_INFINITY);
private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000;
@@ -174,14 +158,6 @@
*/
public class Engine {
IWallpaperEngineWrapper mIWallpaperEngine;
- final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4);
- final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4);
-
- // 2D matrix [x][y] to represent a page of a portion of a window
- EngineWindowPage[] mWindowPages = new EngineWindowPage[1];
- Bitmap mLastScreenshot;
- int mLastWindowPage = -1;
- float mLastPageOffset = 0;
// Copies from mIWallpaperEngine.
HandlerCaller mCaller;
@@ -433,8 +409,8 @@
*/
@VisibleForTesting
public Engine(Supplier<Long> clockFunction, Handler handler) {
- mClockFunction = clockFunction;
- mHandler = handler;
+ mClockFunction = clockFunction;
+ mHandler = handler;
}
/**
@@ -472,19 +448,6 @@
}
/**
- * Return whether the wallpaper is capable of extracting local colors in a rectangle area,
- * Must implement without calling super:
- * {@link #addLocalColorsAreas(List)}
- * {@link #removeLocalColorsAreas(List)}
- * When local colors change, call {@link #notifyLocalColorsChanged(List, List)}
- * See {@link com.android.systemui.ImageWallpaper} for an example
- * @hide
- */
- public boolean supportsLocalColorExtraction() {
- return false;
- }
-
- /**
* Returns true if this engine is running in preview mode -- that is,
* it is being shown to the user before they select it as the actual
* wallpaper.
@@ -728,8 +691,6 @@
Log.w(TAG, "Can't notify system because wallpaper connection "
+ "was not established.");
}
- resetWindowPages();
- processLocalColors(mPendingXOffset, mPendingXOffsetStep);
} catch (RemoteException e) {
Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e);
}
@@ -753,28 +714,6 @@
}
/**
- * Send the changed local color areas for the connection
- * @param regions
- * @param colors
- * @hide
- */
- public void notifyLocalColorsChanged(@NonNull List<RectF> regions,
- @NonNull List<WallpaperColors> colors)
- throws RuntimeException {
- for (int i = 0; i < regions.size() && i < colors.size() && colors.get(i) != null; i++) {
- try {
- mConnection.onLocalWallpaperColorsChanged(
- regions.get(i),
- colors.get(i),
- mDisplayContext.getDisplayId()
- );
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- /**
* Sets internal engine state. Only for testing.
* @param created {@code true} or {@code false}.
* @hide
@@ -1129,9 +1068,7 @@
mIsCreating = false;
mSurfaceCreated = true;
if (redrawNeeded) {
- resetWindowPages();
mSession.finishDrawing(mWindow, null /* postDrawTransaction */);
- processLocalColors(mPendingXOffset, mPendingXOffsetStep);
}
reposition();
mIWallpaperEngine.reportShown();
@@ -1272,7 +1209,6 @@
if (!mDestroyed) {
mVisible = visible;
reportVisibility();
- if (visible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
}
}
@@ -1342,405 +1278,6 @@
} catch (RemoteException e) {
}
}
-
- // setup local color extraction data
- processLocalColors(xOffset, xOffsetStep);
- }
-
- private void processLocalColors(float xOffset, float xOffsetStep) {
- // implemented by the wallpaper
- if (supportsLocalColorExtraction()) return;
- if (DEBUG) {
- Log.d(TAG, "processLocalColors " + xOffset + " of step "
- + xOffsetStep);
- }
- //below is the default implementation
- if (!validStep(xOffsetStep)) {
- if (DEBUG) {
- Log.w(TAG, "invalid offset step " + xOffsetStep);
- }
- return;
- }
-
- int xPage = Math.round(xOffset / xOffsetStep);
- if (!shouldProcessPage(xPage, xOffset, xOffsetStep)) return;
- mLastWindowPage = xPage;
- mLastPageOffset = xOffset;
- int xPages = Math.round(1 / xOffsetStep);
- if (DEBUG) {
- Log.d(TAG, "xPages " + xPages + " xPage " + xPage);
- Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
- }
- EngineWindowPage current;
- synchronized (mLock) {
- if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) {
- mWindowPages = new EngineWindowPage[xPages];
- initWindowPages(mWindowPages, xOffsetStep);
- }
- if (mLocalColorsToAdd.size() != 0) {
- for (RectF colorArea : mLocalColorsToAdd) {
- if (!isValid(colorArea)) continue;
- mLocalColorAreas.add(colorArea);
- int colorPage = getRectFPage(colorArea, xOffsetStep);
- EngineWindowPage currentPage = mWindowPages[colorPage];
- if (currentPage == null) {
- currentPage = new EngineWindowPage();
- currentPage.addArea(colorArea);
- mWindowPages[colorPage] = currentPage;
- } else {
- currentPage.setLastUpdateTime(0);
- currentPage.removeColor(colorArea);
- }
- }
- mLocalColorsToAdd.clear();
- }
- if (xPage >= mWindowPages.length) {
- if (DEBUG) {
- Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage);
- Log.e(TAG, "error on page " + xPage + " out of " + xPages);
- Log.e(TAG,
- "error on xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
- }
- xPage = mWindowPages.length - 1;
- }
- current = mWindowPages[xPage];
- if (current == null) {
- if (DEBUG) Log.d(TAG, "making page " + xPage + " out of " + xPages);
- if (DEBUG) {
- Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
- }
- current = new EngineWindowPage();
- mWindowPages[xPage] = current;
- }
- }
- updatePage(current, xPage, xPages, xOffsetStep);
- }
-
- private boolean shouldProcessPage(int xPage, float xOffset, float xOffsetStep) {
- float pageCenter = xOffsetStep * xPage + xOffsetStep / 2;
-
- return (pageCenter > xOffset && pageCenter < mLastPageOffset)
- || (pageCenter < xOffset && pageCenter > mLastPageOffset);
- }
-
- private void initWindowPages(EngineWindowPage[] windowPages, float step) {
- for (int i = 0; i < windowPages.length; i++) {
- windowPages[i] = new EngineWindowPage();
- }
- mLocalColorAreas.addAll(mLocalColorsToAdd);
- mLocalColorsToAdd.clear();
- for (RectF area: mLocalColorAreas) {
- if (!isValid(area)) {
- mLocalColorAreas.remove(area);
- continue;
- }
- int pageNum = getRectFPage(area, step);
- windowPages[pageNum].addArea(area);
- }
- }
-
- void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages,
- float xOffsetStep) {
- // to save creating a runnable, check twice
- long current = System.nanoTime() / 1_000_000;
- AtomicLong lapsed = new AtomicLong(current - currentPage.getLastUpdateTime());
- if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
- currentPage.execSync((page) -> {
- // just in case of race double check
- long currentInner = System.nanoTime() / 1_000_000;
- lapsed.set(currentInner - page.getLastUpdateTime());
- page.setLastUpdateTime(currentInner);
- });
- long lastUpdate = currentPage.getLastUpdateTime();
- if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
- Surface surface = mSurfaceHolder.getSurface();
- boolean widthIsLarger =
- mSurfaceControl.getWidth() > mSurfaceControl.getHeight();
- int smaller = widthIsLarger ? mSurfaceControl.getWidth()
- : mSurfaceControl.getHeight();
- float ratio = (float) MIN_BITMAP_SCREENSHOT_WIDTH / (float) smaller;
- int width = (int) (ratio * mSurfaceControl.getWidth());
- int height = (int) (ratio * mSurfaceControl.getHeight());
- if (width <= 0 || height <= 0) {
- return;
- }
- Bitmap screenShot = Bitmap.createBitmap(width, height,
- Bitmap.Config.ARGB_8888);
- final Bitmap finalScreenShot = screenShot;
- Trace.beginSection("WallpaperService#pixelCopy");
- PixelCopy.request(surface, screenShot, (res) -> {
- Trace.endSection();
- if (DEBUG) Log.d(TAG, "result of pixel copy is " + res);
- if (res != PixelCopy.SUCCESS) {
- currentPage.execSync((p) -> {
- // reset the time
- p.setLastUpdateTime(lastUpdate);
- // assign the last bitmap taken for now
- p.setBitmap(mLastScreenshot);
- });
- return;
- }
- mLastScreenshot = finalScreenShot;
- // going to hold this lock for a while
- currentPage.execSync((p) -> {
- p.setBitmap(finalScreenShot);
- });
- updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
- }, mHandler);
-
- }
- // locked by the passed page
- private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages,
- float xOffsetStep) {
- if (page.getBitmap() == null) return;
- if (DEBUG) {
- Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas "
- + page.getAreas().size() + " and bitmap size of "
- + page.getBitmap().getWidth() + " x " + page.getBitmap().getHeight());
- }
- for (RectF area: page.getAreas()) {
- RectF subArea = generateSubRect(area, pageIndx, numPages);
- Bitmap b = page.getBitmap();
- int x = Math.round(b.getWidth() * subArea.left);
- int y = Math.round(b.getHeight() * subArea.top);
- int width = Math.round(b.getWidth() * subArea.width());
- int height = Math.round(b.getHeight() * subArea.height());
- Bitmap target;
- try {
- target = Bitmap.createBitmap(page.getBitmap(), x, y, width, height);
- } catch (Exception e) {
- Log.e(TAG, "Error creating page local color bitmap", e);
- continue;
- }
- WallpaperColors color = WallpaperColors.fromBitmap(target);
- target.recycle();
- WallpaperColors currentColor = page.getColors(area);
-
- if (DEBUG) {
- Log.d(TAG, "getting local bitmap area x " + x + " y " + y
- + " width " + width + " height " + height + " for sub area " + subArea
- + " and with page " + pageIndx + " of " + numPages);
-
- }
- if (currentColor == null || !color.equals(currentColor)) {
- page.addWallpaperColors(area, color);
- if (DEBUG) {
- Log.d(TAG, "onLocalWallpaperColorsChanged"
- + " local color callback for area" + area + " for page " + pageIndx
- + " of " + numPages);
- }
- try {
- mConnection.onLocalWallpaperColorsChanged(area, color,
- mDisplayContext.getDisplayId());
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
- }
- }
- }
- }
-
- private RectF generateSubRect(RectF in, int pageInx, int numPages) {
- float minLeft = (float) (pageInx) / (float) (numPages);
- float maxRight = (float) (pageInx + 1) / (float) (numPages);
- float left = in.left;
- float right = in.right;
-
- // bound rect
- if (left < minLeft) left = minLeft;
- if (right > maxRight) right = maxRight;
-
- // scale up the sub area then trim
- left = (left * (float) numPages) % 1f;
- right = (right * (float) numPages) % 1f;
- if (right == 0f) {
- right = 1f;
- }
-
- return new RectF(left, in.top, right, in.bottom);
- }
-
- private void resetWindowPages() {
- if (supportsLocalColorExtraction()) return;
- mLastWindowPage = -1;
- synchronized (mLock) {
- for (int i = 0; i < mWindowPages.length; i++) {
- EngineWindowPage page = mWindowPages[i];
- if (page != null) {
- page.execSync((p) -> {
- p.setLastUpdateTime(0L);
- });
- }
- }
- }
- }
-
- private int getRectFPage(RectF area, float step) {
- if (!isValid(area)) return 0;
- if (!validStep(step)) return 0;
- int pages = Math.round(1 / step);
- int page = Math.round(area.centerX() * pages);
- if (page == pages) return pages - 1;
- if (page == mWindowPages.length) page = mWindowPages.length - 1;
- return page;
- }
-
- /**
- * Add local colors areas of interest
- * @param regions list of areas
- * @hide
- */
- public void addLocalColorsAreas(@NonNull List<RectF> regions) {
- if (supportsLocalColorExtraction()) return;
- if (DEBUG) {
- Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions);
- }
- float step = mPendingXOffsetStep;
-
- List<WallpaperColors> colors = getLocalWallpaperColors(regions);
- synchronized (mLock) {
- if (!validStep(step)) {
- mLocalColorsToAdd.addAll(regions);
- return;
- }
- for (int i = 0; i < regions.size(); i++) {
- RectF area = regions.get(i);
- if (!isValid(area)) continue;
- int pageInx = getRectFPage(area, step);
- // no page should be null
- EngineWindowPage page = mWindowPages[pageInx];
-
- if (page != null) {
- mLocalColorAreas.add(area);
- page.addArea(area);
- WallpaperColors color = colors.get(i);
- if (color != null && !color.equals(page.getColors(area))) {
- page.execSync(p -> {
- p.addWallpaperColors(area, color);
- });
- }
- } else {
- mLocalColorsToAdd.add(area);
- }
- }
- }
-
- for (int i = 0; i < colors.size() && colors.get(i) != null; i++) {
- try {
- mConnection.onLocalWallpaperColorsChanged(regions.get(i), colors.get(i),
- mDisplayContext.getDisplayId());
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
- return;
- }
- }
- }
-
- /**
- * Remove local colors areas of interest if they exist
- * @param regions list of areas
- * @hide
- */
- public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
- if (supportsLocalColorExtraction()) return;
- synchronized (mLock) {
- float step = mPendingXOffsetStep;
- mLocalColorsToAdd.removeAll(regions);
- mLocalColorAreas.removeAll(regions);
- if (!validStep(step)) {
- return;
- }
- for (int i = 0; i < regions.size(); i++) {
- RectF area = regions.get(i);
- if (!isValid(area)) continue;
- int pageInx = getRectFPage(area, step);
- // no page should be null
- EngineWindowPage page = mWindowPages[pageInx];
- if (page != null) {
- page.execSync(p -> {
- p.removeArea(area);
- });
- }
- }
- }
- }
-
- private @NonNull List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas) {
- ArrayList<WallpaperColors> colors = new ArrayList<>(areas.size());
- float step = mPendingXOffsetStep;
- if (!validStep(step)) {
- if (DEBUG) Log.d(TAG, "invalid step size " + step);
- step = 1.0f;
- }
- for (int i = 0; i < areas.size(); i++) {
- RectF currentArea = areas.get(i);
- EngineWindowPage page;
- RectF area;
- int pageIndx;
- synchronized (mLock) {
- pageIndx = getRectFPage(currentArea, step);
- if (mWindowPages.length == 0 || pageIndx < 0
- || pageIndx > mWindowPages.length || !isValid(currentArea)) {
- colors.add(null);
- continue;
- }
- area = generateSubRect(currentArea, pageIndx, mWindowPages.length);
- page = mWindowPages[pageIndx];
- }
- if (page == null) {
- colors.add(null);
- continue;
- }
- float finalStep = step;
- int finalPageIndx = pageIndx;
- Bitmap screenShot = page.getBitmap();
- if (screenShot == null || screenShot.isRecycled()) {
- if (DEBUG) {
- Log.d(TAG, "invalid bitmap " + screenShot
- + " for page " + finalPageIndx);
- }
- page.setLastUpdateTime(0);
- colors.add(null);
- continue;
- }
- Bitmap b = screenShot;
- Rect subImage = new Rect(
- Math.round(area.left * b.getWidth() / finalStep),
- Math.round(area.top * b.getHeight()),
- Math.round(area.right * b.getWidth() / finalStep),
- Math.round(area.bottom * b.getHeight())
- );
- subImage = fixRect(b, subImage);
- if (DEBUG) {
- Log.d(TAG, "getting subbitmap of " + subImage.toString()
- + " for RectF " + area.toString()
- + " screenshot width " + screenShot.getWidth() + " height "
- + screenShot.getHeight());
- }
- Bitmap colorImg = Bitmap.createBitmap(screenShot,
- subImage.left, subImage.top, subImage.width(), subImage.height());
- if (DEBUG) {
- Log.d(TAG, "created bitmap " + colorImg.getWidth() + ", "
- + colorImg.getHeight());
- }
- WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
- colors.add(color);
- }
- return colors;
- }
-
- // fix the rect to be included within the bounds of the bitmap
- private Rect fixRect(Bitmap b, Rect r) {
- r.left = r.left >= r.right || r.left >= b.getWidth() || r.left > 0
- ? 0
- : r.left;
- r.right = r.left >= r.right || r.right > b.getWidth()
- ? b.getWidth()
- : r.right;
- return r;
- }
-
- private boolean validStep(float step) {
- return !PROHIBITED_STEPS.contains(step) && step > 0. && step <= 1.;
}
void doCommand(WallpaperCommand cmd) {
@@ -1834,16 +1371,6 @@
};
}
- private boolean isValid(RectF area) {
- boolean valid = area.bottom > area.top && area.left < area.right
- && LOCAL_COLOR_BOUNDS.contains(area);
- return valid;
- }
-
- private boolean inRectFRange(float number) {
- return number >= 0f && number <= 1f;
- }
-
class IWallpaperEngineWrapper extends IWallpaperEngine.Stub
implements HandlerCaller.Callback {
private final HandlerCaller mCaller;
@@ -1950,14 +1477,6 @@
mCaller.sendMessage(msg);
}
- public void addLocalColorsAreas(List<RectF> regions) {
- mEngine.addLocalColorsAreas(regions);
- }
-
- public void removeLocalColorsAreas(List<RectF> regions) {
- mEngine.removeLocalColorsAreas(regions);
- }
-
public void destroy() {
Message msg = mCaller.obtainMessage(DO_DETACH);
mCaller.sendMessage(msg);
@@ -1997,15 +1516,14 @@
}
switch (message.what) {
case DO_ATTACH: {
- Engine engine = onCreateEngine();
- mEngine = engine;
try {
mConnection.attachEngine(this, mDisplayId);
} catch (RemoteException e) {
- engine.detach();
Log.w(TAG, "Wallpaper host disappeared", e);
return;
}
+ Engine engine = onCreateEngine();
+ mEngine = engine;
mActiveEngines.add(engine);
engine.attach(this);
return;
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/core/java/android/speech/tts/ITextToSpeechManager.aidl
similarity index 63%
copy from core/java/android/app/ILocalWallpaperColorConsumer.aidl
copy to core/java/android/speech/tts/ITextToSpeechManager.aidl
index 28b11ec..e6b63df 100644
--- a/core/java/android/app/ILocalWallpaperColorConsumer.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechManager.aidl
@@ -14,14 +14,16 @@
* limitations under the License.
*/
-package android.app;
+package android.speech.tts;
-import android.app.WallpaperColors;
-import android.graphics.RectF;
+import android.speech.tts.ITextToSpeechSessionCallback;
/**
+ * TextToSpeechManagerService interface. Allows opening {@link TextToSpeech} session with the
+ * specified provider proxied by the system service.
+ *
* @hide
*/
-oneway interface ILocalWallpaperColorConsumer {
- void onColorsChanged(in RectF area, in WallpaperColors colors);
-}
\ No newline at end of file
+oneway interface ITextToSpeechManager {
+ void createSession(in String engine, in ITextToSpeechSessionCallback managerCallback);
+}
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/core/java/android/speech/tts/ITextToSpeechSession.aidl
similarity index 64%
copy from core/java/android/app/ILocalWallpaperColorConsumer.aidl
copy to core/java/android/speech/tts/ITextToSpeechSession.aidl
index 28b11ec..b2afeb0 100644
--- a/core/java/android/app/ILocalWallpaperColorConsumer.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechSession.aidl
@@ -14,14 +14,20 @@
* limitations under the License.
*/
-package android.app;
-
-import android.app.WallpaperColors;
-import android.graphics.RectF;
+package android.speech.tts;
/**
- * @hide
+ * TextToSpeech session interface. Allows to control remote TTS service session once connected.
+ *
+ * @see ITextToSpeechManager
+ * @see ITextToSpeechSessionCallback
+ *
+ * {@hide}
*/
-oneway interface ILocalWallpaperColorConsumer {
- void onColorsChanged(in RectF area, in WallpaperColors colors);
+oneway interface ITextToSpeechSession {
+
+ /**
+ * Disconnects the client from the TTS provider.
+ */
+ void disconnect();
}
\ No newline at end of file
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
similarity index 63%
copy from core/java/android/app/ILocalWallpaperColorConsumer.aidl
copy to core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
index 28b11ec..545622a 100644
--- a/core/java/android/app/ILocalWallpaperColorConsumer.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechSessionCallback.aidl
@@ -14,14 +14,19 @@
* limitations under the License.
*/
-package android.app;
-
-import android.app.WallpaperColors;
-import android.graphics.RectF;
+package android.speech.tts;
+import android.speech.tts.ITextToSpeechSession;
/**
+ * Callback interface for a session created by {@link ITextToSpeechManager} API.
+ *
* @hide
*/
-oneway interface ILocalWallpaperColorConsumer {
- void onColorsChanged(in RectF area, in WallpaperColors colors);
+oneway interface ITextToSpeechSessionCallback {
+
+ void onConnected(in ITextToSpeechSession session, in IBinder serviceBinder);
+
+ void onDisconnected();
+
+ void onError(in String errorInfo);
}
\ No newline at end of file
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 7a18538..5d66dc7 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -35,6 +35,7 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;
@@ -51,6 +52,7 @@
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
*
@@ -695,6 +697,8 @@
public static final String KEY_FEATURE_NETWORK_RETRIES_COUNT = "networkRetriesCount";
}
+ private static final boolean DEBUG = false;
+
private final Context mContext;
@UnsupportedAppUsage
private Connection mConnectingServiceConnection;
@@ -716,6 +720,9 @@
private final Map<CharSequence, Uri> mUtterances;
private final Bundle mParams = new Bundle();
private final TtsEngines mEnginesHelper;
+ private final boolean mIsSystem;
+ @Nullable private final Executor mInitExecutor;
+
@UnsupportedAppUsage
private volatile String mCurrentEngine = null;
@@ -758,8 +765,21 @@
*/
public TextToSpeech(Context context, OnInitListener listener, String engine,
String packageName, boolean useFallback) {
+ this(context, /* initExecutor= */ null, listener, engine, packageName,
+ useFallback, /* isSystem= */ true);
+ }
+
+ /**
+ * Used internally to instantiate TextToSpeech objects.
+ *
+ * @hide
+ */
+ private TextToSpeech(Context context, @Nullable Executor initExecutor,
+ OnInitListener initListener, String engine, String packageName, boolean useFallback,
+ boolean isSystem) {
mContext = context;
- mInitListener = listener;
+ mInitExecutor = initExecutor;
+ mInitListener = initListener;
mRequestedEngine = engine;
mUseFallback = useFallback;
@@ -768,6 +788,9 @@
mUtteranceProgressListener = null;
mEnginesHelper = new TtsEngines(mContext);
+
+ mIsSystem = isSystem;
+
initTts();
}
@@ -842,10 +865,14 @@
}
private boolean connectToEngine(String engine) {
- Connection connection = new Connection();
- Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
- intent.setPackage(engine);
- boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+ Connection connection;
+ if (mIsSystem) {
+ connection = new SystemConnection();
+ } else {
+ connection = new DirectConnection();
+ }
+
+ boolean bound = connection.connect(engine);
if (!bound) {
Log.e(TAG, "Failed to bind to " + engine);
return false;
@@ -857,11 +884,19 @@
}
private void dispatchOnInit(int result) {
- synchronized (mStartLock) {
- if (mInitListener != null) {
- mInitListener.onInit(result);
- mInitListener = null;
+ Runnable onInitCommand = () -> {
+ synchronized (mStartLock) {
+ if (mInitListener != null) {
+ mInitListener.onInit(result);
+ mInitListener = null;
+ }
}
+ };
+
+ if (mInitExecutor != null) {
+ mInitExecutor.execute(onInitCommand);
+ } else {
+ onInitCommand.run();
}
}
@@ -2127,13 +2162,17 @@
return mEnginesHelper.getEngines();
}
- private class Connection implements ServiceConnection {
+ private abstract class Connection implements ServiceConnection {
private ITextToSpeechService mService;
private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
private boolean mEstablished;
+ abstract boolean connect(String engine);
+
+ abstract void disconnect();
+
private final ITextToSpeechCallback.Stub mCallback =
new ITextToSpeechCallback.Stub() {
public void onStop(String utteranceId, boolean isStarted)
@@ -2199,11 +2238,6 @@
};
private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
- private final ComponentName mName;
-
- public SetupConnectionAsyncTask(ComponentName name) {
- mName = name;
- }
@Override
protected Integer doInBackground(Void... params) {
@@ -2227,7 +2261,7 @@
mParams.putString(Engine.KEY_PARAM_VOICE_NAME, defaultVoiceName);
}
- Log.i(TAG, "Set up connection to " + mName);
+ Log.i(TAG, "Setting up the connection to TTS engine...");
return SUCCESS;
} catch (RemoteException re) {
Log.e(TAG, "Error connecting to service, setCallback() failed");
@@ -2249,11 +2283,11 @@
}
@Override
- public void onServiceConnected(ComponentName name, IBinder service) {
+ public void onServiceConnected(ComponentName componentName, IBinder service) {
synchronized(mStartLock) {
mConnectingServiceConnection = null;
- Log.i(TAG, "Connected to " + name);
+ Log.i(TAG, "Connected to TTS engine");
if (mOnSetupConnectionAsyncTask != null) {
mOnSetupConnectionAsyncTask.cancel(false);
@@ -2263,7 +2297,7 @@
mServiceConnection = Connection.this;
mEstablished = false;
- mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
+ mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask();
mOnSetupConnectionAsyncTask.execute();
}
}
@@ -2277,7 +2311,7 @@
*
* @return true if we cancel mOnSetupConnectionAsyncTask in progress.
*/
- private boolean clearServiceConnection() {
+ protected boolean clearServiceConnection() {
synchronized(mStartLock) {
boolean result = false;
if (mOnSetupConnectionAsyncTask != null) {
@@ -2295,8 +2329,8 @@
}
@Override
- public void onServiceDisconnected(ComponentName name) {
- Log.i(TAG, "Asked to disconnect from " + name);
+ public void onServiceDisconnected(ComponentName componentName) {
+ Log.i(TAG, "Disconnected from TTS engine");
if (clearServiceConnection()) {
/* We need to protect against a rare case where engine
* dies just after successful connection - and we process onServiceDisconnected
@@ -2308,11 +2342,6 @@
}
}
- public void disconnect() {
- mContext.unbindService(this);
- clearServiceConnection();
- }
-
public boolean isEstablished() {
return mService != null && mEstablished;
}
@@ -2342,6 +2371,91 @@
}
}
+ // Currently all the clients are routed through the System connection. Direct connection
+ // is left for debugging, testing and benchmarking purposes.
+ // TODO(b/179599071): Remove direct connection once system one is fully tested.
+ private class DirectConnection extends Connection {
+ @Override
+ boolean connect(String engine) {
+ Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
+ intent.setPackage(engine);
+ return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ void disconnect() {
+ mContext.unbindService(this);
+ clearServiceConnection();
+ }
+ }
+
+ private class SystemConnection extends Connection {
+
+ @Nullable
+ private volatile ITextToSpeechSession mSession;
+
+ @Override
+ boolean connect(String engine) {
+ IBinder binder = ServiceManager.getService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE);
+
+ ITextToSpeechManager manager = ITextToSpeechManager.Stub.asInterface(binder);
+
+ if (manager == null) {
+ Log.e(TAG, "System service is not available!");
+ return false;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Connecting to engine: " + engine);
+ }
+
+ try {
+ manager.createSession(engine, new ITextToSpeechSessionCallback.Stub() {
+ @Override
+ public void onConnected(ITextToSpeechSession session, IBinder serviceBinder) {
+ mSession = session;
+ onServiceConnected(
+ /* componentName= */ null,
+ serviceBinder);
+ }
+
+ @Override
+ public void onDisconnected() {
+ onServiceDisconnected(/* componentName= */ null);
+ }
+
+ @Override
+ public void onError(String errorInfo) {
+ Log.w(TAG, "System TTS connection error: " + errorInfo);
+ // The connection was not established successfully - handle as
+ // disconnection: clear the state and notify the user.
+ onServiceDisconnected(/* componentName= */ null);
+ }
+ });
+
+ return true;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Error communicating with the System Server: ", ex);
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ void disconnect() {
+ ITextToSpeechSession session = mSession;
+
+ if (session != null) {
+ try {
+ session.disconnect();
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Error disconnecting session", ex);
+ }
+
+ clearServiceConnection();
+ }
+ }
+ }
+
private interface Action<R> {
R run(ITextToSpeechService service) throws RemoteException;
}
diff --git a/core/java/android/uwb/RangingSession.java b/core/java/android/uwb/RangingSession.java
index bfa8bf2..52ec5bd 100644
--- a/core/java/android/uwb/RangingSession.java
+++ b/core/java/android/uwb/RangingSession.java
@@ -16,8 +16,10 @@
package android.uwb;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.os.Binder;
import android.os.PersistableBundle;
@@ -247,6 +249,7 @@
*
* @param params configuration parameters for starting the session
*/
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public void start(@NonNull PersistableBundle params) {
if (mState != State.IDLE) {
throw new IllegalStateException();
@@ -271,6 +274,7 @@
*
* @param params the parameters to reconfigure and their new values
*/
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public void reconfigure(@NonNull PersistableBundle params) {
if (mState != State.ACTIVE && mState != State.IDLE) {
throw new IllegalStateException();
@@ -302,6 +306,7 @@
* <p>On failure to stop the session,
* {@link RangingSession.Callback#onStopFailed(int, PersistableBundle)} is invoked.
*/
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public void stop() {
if (mState != State.ACTIVE) {
throw new IllegalStateException();
@@ -333,6 +338,7 @@
* {@link #close()}, even if the {@link RangingSession} is already closed.
*/
@Override
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public void close() {
if (mState == State.CLOSED) {
mExecutor.execute(() -> mCallback.onClosed(
diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java
index 8adfe06..2dc0ba0 100644
--- a/core/java/android/uwb/UwbManager.java
+++ b/core/java/android/uwb/UwbManager.java
@@ -16,9 +16,11 @@
package android.uwb;
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -154,6 +156,7 @@
* @param executor an {@link Executor} to execute given callback
* @param callback user implementation of the {@link AdapterStateCallback}
*/
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public void registerAdapterStateCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull AdapterStateCallback callback) {
mAdapterStateListener.register(executor, callback);
@@ -168,6 +171,7 @@
*
* @param callback user implementation of the {@link AdapterStateCallback}
*/
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public void unregisterAdapterStateCallback(@NonNull AdapterStateCallback callback) {
mAdapterStateListener.unregister(callback);
}
@@ -181,6 +185,7 @@
* @return {@link PersistableBundle} of the device's supported UWB protocols and parameters
*/
@NonNull
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public PersistableBundle getSpecificationInfo() {
try {
return mUwbAdapter.getSpecificationInfo();
@@ -194,6 +199,7 @@
*
* @return true if ranging is supported
*/
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public boolean isRangingSupported() {
try {
return mUwbAdapter.isRangingSupported();
@@ -250,6 +256,7 @@
* @return angle of arrival type supported
*/
@AngleOfArrivalSupportType
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public int getAngleOfArrivalSupport() {
try {
switch (mUwbAdapter.getAngleOfArrivalSupport()) {
@@ -281,6 +288,7 @@
* @return {@link List} of supported channel numbers ordered by preference
*/
@NonNull
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public List<Integer> getSupportedChannelNumbers() {
List<Integer> channels = new ArrayList<>();
try {
@@ -300,6 +308,7 @@
* @return {@link List} of supported preamble code indices
*/
@NonNull
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public Set<Integer> getSupportedPreambleCodeIndices() {
Set<Integer> preambles = new HashSet<>();
try {
@@ -320,6 +329,7 @@
* @return the timestamp resolution in nanoseconds
*/
@SuppressLint("MethodNameUnits")
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public long elapsedRealtimeResolutionNanos() {
try {
return mUwbAdapter.getTimestampResolutionNanos();
@@ -333,6 +343,7 @@
*
* @return the maximum allowed number of simultaneously open {@link RangingSession} instances.
*/
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public int getMaxSimultaneousSessions() {
try {
return mUwbAdapter.getMaxSimultaneousSessions();
@@ -347,6 +358,7 @@
*
* @return the maximum number of remote devices per {@link RangingSession}
*/
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public int getMaxRemoteDevicesPerInitiatorSession() {
try {
return mUwbAdapter.getMaxRemoteDevicesPerInitiatorSession();
@@ -361,6 +373,7 @@
*
* @return the maximum number of remote devices per {@link RangingSession}
*/
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public int getMaxRemoteDevicesPerResponderSession() {
try {
return mUwbAdapter.getMaxRemoteDevicesPerResponderSession();
@@ -396,6 +409,7 @@
* {@link RangingSession.Callback#onOpened(RangingSession)}.
*/
@NonNull
+ @RequiresPermission(Manifest.permission.UWB_PRIVILEGED)
public AutoCloseable openRangingSession(@NonNull PersistableBundle parameters,
@NonNull @CallbackExecutor Executor executor,
@NonNull RangingSession.Callback callbacks) {
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 5937499..6543de1 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -154,6 +154,24 @@
*/
public static final int VSYNC_TIMESTAMP = 11;
+ /**
+ * Metric identifier for GPU duration.
+ * <p>
+ * Represents the total time in nanoseconds this frame took to complete on the GPU.
+ * </p>
+ **/
+ public static final int GPU_DURATION = 12;
+
+ /**
+ * Metric identifier for the total duration that was available to the app to produce a frame.
+ * <p>
+ * Represents the total time in nanoseconds the system allocated for the app to produce its
+ * frame. If FrameMetrics.TOTAL_DURATION < FrameMetrics.DEADLINE, the app hit its intended
+ * deadline and there was no jank visible to the user.
+ * </p>
+ **/
+ public static final int DEADLINE = 13;
+
private static final int FRAME_INFO_FLAG_FIRST_DRAW = 1 << 0;
/**
@@ -175,6 +193,8 @@
FIRST_DRAW_FRAME,
INTENDED_VSYNC_TIMESTAMP,
VSYNC_TIMESTAMP,
+ GPU_DURATION,
+ DEADLINE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Metric {}
@@ -205,6 +225,8 @@
Index.ISSUE_DRAW_COMMANDS_START,
Index.SWAP_BUFFERS,
Index.FRAME_COMPLETED,
+ Index.GPU_COMPLETED,
+ Index.SWAP_BUFFERS_COMPLETED
})
@Retention(RetentionPolicy.SOURCE)
public @interface Index {
@@ -224,8 +246,10 @@
int ISSUE_DRAW_COMMANDS_START = 13;
int SWAP_BUFFERS = 14;
int FRAME_COMPLETED = 15;
+ int GPU_COMPLETED = 18;
+ int SWAP_BUFFERS_COMPLETED = 19;
- int FRAME_STATS_COUNT = 19; // must always be last and in sync with
+ int FRAME_STATS_COUNT = 20; // must always be last and in sync with
// FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h
}
@@ -251,9 +275,19 @@
// COMMAND_ISSUE
Index.ISSUE_DRAW_COMMANDS_START, Index.SWAP_BUFFERS,
// SWAP_BUFFERS
- Index.SWAP_BUFFERS, Index.FRAME_COMPLETED,
+ Index.SWAP_BUFFERS, Index.SWAP_BUFFERS_COMPLETED,
// TOTAL_DURATION
Index.INTENDED_VSYNC, Index.FRAME_COMPLETED,
+ // RESERVED for FIRST_DRAW_FRAME
+ 0, 0,
+ // RESERVED forINTENDED_VSYNC_TIMESTAMP
+ 0, 0,
+ // RESERVED VSYNC_TIMESTAMP
+ 0, 0,
+ // GPU_DURATION
+ Index.SWAP_BUFFERS, Index.GPU_COMPLETED,
+ // DEADLINE
+ Index.INTENDED_VSYNC, Index.FRAME_DEADLINE,
};
/**
@@ -294,7 +328,7 @@
* @return the value of the metric or -1 if it is not available.
*/
public long getMetric(@Metric int id) {
- if (id < UNKNOWN_DELAY_DURATION || id > VSYNC_TIMESTAMP) {
+ if (id < UNKNOWN_DELAY_DURATION || id > DEADLINE) {
return -1;
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 6a629ca..66b9617 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -2404,7 +2404,7 @@
if (Float.isNaN(brightness) || brightness > 1.0f
|| (brightness < 0.0f && brightness != -1.0f)) {
throw new IllegalArgumentException("brightness must be a number between 0.0f and 1.0f,"
- + " or -1 to turn the backlight off.");
+ + " or -1 to turn the backlight off: " + brightness);
}
return nativeSetDisplayBrightness(displayToken, brightness);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ba78f96..44d4d6b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24032,6 +24032,7 @@
* @see #getBackgroundTintMode()
* @see Drawable#setTintBlendMode(BlendMode)
*/
+ @RemotableViewMethod
public void setBackgroundTintBlendMode(@Nullable BlendMode blendMode) {
if (mBackgroundTint == null) {
mBackgroundTint = new TintInfo();
@@ -24294,6 +24295,7 @@
* @see #getForegroundTintMode()
* @see Drawable#setTintBlendMode(BlendMode)
*/
+ @RemotableViewMethod
public void setForegroundTintBlendMode(@Nullable BlendMode blendMode) {
if (mForegroundInfo == null) {
mForegroundInfo = new ForegroundInfo();
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 7b2bb73..a8fff8b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -749,7 +749,7 @@
@Override
public boolean hasActiveConnection(View view) {
synchronized (mH) {
- if (!hasServedByInputMethodLocked(view)) {
+ if (!hasServedByInputMethodLocked(view) || mCurMethod == null) {
return false;
}
@@ -765,6 +765,17 @@
return mDelegate;
}
+ /**
+ * Checks whether the active input connection (if any) is for the given view.
+ *
+ * @hide
+ * @see ImeFocusController#getImmDelegate()#hasActiveInputConnection(View)
+ */
+ @TestApi
+ public boolean hasActiveInputConnection(@Nullable View view) {
+ return mDelegate.hasActiveConnection(view);
+ }
+
private View getServedViewLocked() {
return mCurRootView != null ? mCurRootView.getImeFocusController().getServedView() : null;
}
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 2e75834..bc2b221 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -31,6 +31,7 @@
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.Trace;
import android.util.AndroidRuntimeException;
import android.util.ArraySet;
@@ -261,7 +262,7 @@
// us honest and minimize usage of WebView internals when binding the proxy.
if (sProviderInstance != null) return sProviderInstance;
- sTimestamps[WEBVIEW_LOAD_START] = System.currentTimeMillis();
+ sTimestamps[WEBVIEW_LOAD_START] = SystemClock.elapsedRealtime();
final int uid = android.os.Process.myUid();
if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
|| uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
@@ -401,7 +402,7 @@
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
"initialApplication.createApplicationContext");
- sTimestamps[CREATE_CONTEXT_START] = System.currentTimeMillis();
+ sTimestamps[CREATE_CONTEXT_START] = SystemClock.elapsedRealtime();
try {
// Construct an app context to load the Java code into the current app.
Context webViewContext = initialApplication.createApplicationContext(
@@ -410,7 +411,7 @@
sPackageInfo = newPackageInfo;
return webViewContext;
} finally {
- sTimestamps[CREATE_CONTEXT_END] = System.currentTimeMillis();
+ sTimestamps[CREATE_CONTEXT_END] = SystemClock.elapsedRealtime();
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (RemoteException | PackageManager.NameNotFoundException e) {
@@ -436,26 +437,26 @@
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
try {
- sTimestamps[ADD_ASSETS_START] = System.currentTimeMillis();
+ sTimestamps[ADD_ASSETS_START] = SystemClock.elapsedRealtime();
for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
}
sTimestamps[ADD_ASSETS_END] = sTimestamps[GET_CLASS_LOADER_START] =
- System.currentTimeMillis();
+ SystemClock.elapsedRealtime();
ClassLoader clazzLoader = webViewContext.getClassLoader();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
sTimestamps[GET_CLASS_LOADER_END] = sTimestamps[NATIVE_LOAD_START] =
- System.currentTimeMillis();
+ SystemClock.elapsedRealtime();
WebViewLibraryLoader.loadNativeLibrary(clazzLoader,
getWebViewLibrary(sPackageInfo.applicationInfo));
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
sTimestamps[NATIVE_LOAD_END] = sTimestamps[PROVIDER_CLASS_FOR_NAME_START] =
- System.currentTimeMillis();
+ SystemClock.elapsedRealtime();
try {
return getWebViewProviderClass(clazzLoader);
} finally {
- sTimestamps[PROVIDER_CLASS_FOR_NAME_END] = System.currentTimeMillis();
+ sTimestamps[PROVIDER_CLASS_FOR_NAME_END] = SystemClock.elapsedRealtime();
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (ClassNotFoundException e) {
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index ed20d26..0a08ccd 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -695,6 +695,7 @@
* @see #getImageTintMode()
* @see Drawable#setTintBlendMode(BlendMode)
*/
+ @RemotableViewMethod
public void setImageTintBlendMode(@Nullable BlendMode blendMode) {
mDrawableBlendMode = blendMode;
mHasDrawableBlendMode = true;
diff --git a/core/java/android/widget/ImeAwareEditText.java b/core/java/android/widget/ImeAwareEditText.java
index 9cd4585..0d98085 100644
--- a/core/java/android/widget/ImeAwareEditText.java
+++ b/core/java/android/widget/ImeAwareEditText.java
@@ -80,7 +80,7 @@
public void scheduleShowSoftInput() {
final InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
- if (imm.isActive(this)) {
+ if (imm.hasActiveInputConnection(this)) {
// This means that ImeAwareEditText is already connected to the IME.
// InputMethodManager#showSoftInput() is guaranteed to pass client-side focus check.
mHasPendingShowSoftInputRequest = false;
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 67216f59..a44808e 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -823,6 +823,7 @@
* @see #setIndeterminateTintList(ColorStateList)
* @see Drawable#setTintBlendMode(BlendMode)
*/
+ @RemotableViewMethod
public void setIndeterminateTintBlendMode(@Nullable BlendMode blendMode) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
@@ -1132,6 +1133,7 @@
* @see #getProgressTintMode()
* @see Drawable#setTintBlendMode(BlendMode)
*/
+ @RemotableViewMethod
public void setProgressTintBlendMode(@Nullable BlendMode blendMode) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
@@ -1248,6 +1250,7 @@
* @see #setProgressBackgroundTintList(ColorStateList)
* @see Drawable#setTintBlendMode(BlendMode)
*/
+ @RemotableViewMethod
public void setProgressBackgroundTintBlendMode(@Nullable BlendMode blendMode) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
@@ -1360,6 +1363,7 @@
* @see #setSecondaryProgressTintList(ColorStateList)
* @see Drawable#setTintBlendMode(BlendMode)
*/
+ @RemotableViewMethod
public void setSecondaryProgressTintBlendMode(@Nullable BlendMode blendMode) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 4e3d99b..81572b5 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -47,6 +47,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
+import android.graphics.BlendMode;
import android.graphics.Outline;
import android.graphics.PointF;
import android.graphics.PorterDuff;
@@ -1054,6 +1055,8 @@
return ColorStateList.class;
case BaseReflectionAction.ICON:
return Icon.class;
+ case BaseReflectionAction.BLEND_MODE:
+ return BlendMode.class;
default:
return null;
}
@@ -1400,6 +1403,7 @@
static final int INTENT = 14;
static final int COLOR_STATE_LIST = 15;
static final int ICON = 16;
+ static final int BLEND_MODE = 17;
@UnsupportedAppUsage
String methodName;
@@ -1589,6 +1593,10 @@
break;
case ICON:
this.value = in.readTypedObject(Icon.CREATOR);
+ break;
+ case BLEND_MODE:
+ this.value = BlendMode.fromValue(in.readInt());
+ break;
default:
break;
}
@@ -1632,6 +1640,9 @@
case BUNDLE:
out.writeBundle((Bundle) this.value);
break;
+ case BLEND_MODE:
+ out.writeInt(BlendMode.toValue((BlendMode) this.value));
+ break;
case URI:
case BITMAP:
case INTENT:
@@ -4132,6 +4143,18 @@
}
/**
+ * Call a method taking one BlendMode on a view in the layout for this RemoteViews.
+ *
+ * @param viewId The id of the view on which to call the method.
+ * @param methodName The name of the method to call.
+ * @param value The value to pass to the method.
+ */
+ public void setBlendMode(@IdRes int viewId, @NonNull String methodName,
+ @Nullable BlendMode value) {
+ addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BLEND_MODE, value));
+ }
+
+ /**
* Call a method taking one Bundle on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 6983d35c..d72a0ec 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -49,11 +49,12 @@
"libhwui",
"liblog",
"libminikin",
- "libnativehelper",
"libz",
"libziparchive",
],
+ static_libs: ["libnativehelper_lazy"],
+
export_include_dirs: [
".",
"include",
@@ -272,12 +273,13 @@
"libstatspull",
],
export_shared_lib_headers: [
- // AndroidRuntime.h depends on nativehelper/jni.h
- "libnativehelper",
-
// our headers include libnativewindow's public headers
"libnativewindow",
],
+ export_static_lib_headers: [
+ // AndroidRuntime.h depends on nativehelper/jni.h
+ "libnativehelper_lazy",
+ ],
header_libs: [
"bionic_libc_platform_headers",
"dnsproxyd_protocol_headers",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6456e4e..62e077f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1313,11 +1313,13 @@
android:protectionLevel="dangerous|instant" />
<!-- ====================================================================== -->
- <!-- Permissions for accessing the UCE Service -->
+ <!-- Permissions for accessing the vendor UCE Service -->
<!-- ====================================================================== -->
<!-- @hide Allows an application to Access UCE-Presence.
<p>Protection level: signature|privileged
+ @deprecated Framework should no longer use this permission to access the vendor UCE service
+ using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
-->
<permission android:name="android.permission.ACCESS_UCE_PRESENCE_SERVICE"
android:permissionGroup="android.permission-group.PHONE"
@@ -1325,6 +1327,8 @@
<!-- @hide Allows an application to Access UCE-OPTIONS.
<p>Protection level: signature|privileged
+ @deprecated Framework should no longer use this permission to access the vendor UCE service
+ using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
-->
<permission android:name="android.permission.ACCESS_UCE_OPTIONS_SERVICE"
android:permissionGroup="android.permission-group.PHONE"
@@ -2011,6 +2015,12 @@
<permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows access to ultra wideband device.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.UWB_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
<!-- ================================== -->
<!-- Permissions for accessing accounts -->
<!-- ================================== -->
@@ -2457,6 +2467,15 @@
<permission android:name="android.permission.BIND_GBA_SERVICE"
android:protectionLevel="signature" />
+ <!-- Required for an Application to access APIs related to RCS User Capability Exchange.
+ <p> This permission is only granted to system applications fulfilling the SMS, Dialer, and
+ Contacts app roles.
+ <p>Protection level: internal|role
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"
+ android:protectionLevel="internal|role" />
+
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
<!-- ================================== -->
@@ -5496,6 +5515,12 @@
<permission android:name="android.permission.GET_PEOPLE_TILE_PREVIEW"
android:protectionLevel="signature|recents" />
+ <!-- @hide @SystemApi Allows an application to retrieve whether shortcut is backed by a
+ Conversation.
+ TODO(b/180412052): STOPSHIP: Define a role so it can be granted to Shell and AiAi. -->
+ <permission android:name="android.permission.READ_PEOPLE_DATA"
+ android:protectionLevel="signature|appPredictor|recents"/>
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 67b810e..69bb20c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8010,6 +8010,14 @@
<attr name="minResizeWidth" format="dimension"/>
<!-- Minimum height that the AppWidget can be resized to. -->
<attr name="minResizeHeight" format="dimension"/>
+ <!-- Maximum width that the AppWidget can be resized to. -->
+ <attr name="maxResizeWidth" format="dimension"/>
+ <!-- Maximum height that the AppWidget can be resized to. -->
+ <attr name="maxResizeHeight" format="dimension"/>
+ <!-- Default width of the AppWidget in units of launcher grid cells. -->
+ <attr name="targetCellWidth" format="integer"/>
+ <!-- Default height of the AppWidget in units of launcher grid cells. -->
+ <attr name="targetCellHeight" format="integer"/>
<!-- Update period in milliseconds, or 0 if the AppWidget will update itself. -->
<attr name="updatePeriodMillis" format="integer" />
<!-- A resource id of a layout. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dd048f3..faa2157 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1948,6 +1948,8 @@
<string name="config_systemAutomotiveCluster" translatable="false"></string>
<!-- The name of the package that will hold the system shell role. -->
<string name="config_systemShell" translatable="false">com.android.shell</string>
+ <!-- The name of the package that will hold the system contacts role. -->
+ <string name="config_systemContacts" translatable="false">com.android.contacts</string>
<!-- The name of the package that will be allowed to change its components' label/icon. -->
<string name="config_overrideComponentUiPackage" translatable="false"></string>
@@ -2509,10 +2511,9 @@
<string name="config_ethernet_tcp_buffers" translatable="false">524288,1048576,3145728,524288,1048576,2097152</string>
<!-- What source to use to estimate link upstream and downstream bandwidth capacities.
- Default is carrier_config, but it should be set to modem if the modem is returning
- predictive (instead of instantaneous) bandwidth estimate.
- Values are carrier_config and modem. -->
- <string name="config_bandwidthEstimateSource">carrier_config</string>
+ Default is bandwidth_estimator.
+ Values are bandwidth_estimator, carrier_config and modem. -->
+ <string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
<!-- Whether WiFi display is supported by this device.
There are many prerequisites for this feature to work correctly.
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index fdef08b..e43aa3d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3072,6 +3072,10 @@
<public name="windowSplashScreenBrandingImage"/>
<public name="splashScreenTheme" />
<public name="rippleStyle" />
+ <public name="maxResizeWidth" />
+ <public name="maxResizeHeight" />
+ <public name="targetCellWidth" />
+ <public name="targetCellHeight" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
@@ -3134,6 +3138,8 @@
<public name="config_systemAutomotiveProjection" />
<!-- @hide @SystemApi -->
<public name="config_systemShell" />
+ <!-- @hide @SystemApi -->
+ <public name="config_systemContacts" />
</public-group>
<public-group type="id" first-id="0x01020055">
diff --git a/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
index be38260..bc7be1b 100644
--- a/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
+++ b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
@@ -16,14 +16,8 @@
package android.provider;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.testng.Assert.assertThrows;
-import android.content.ContentValues;
-import android.os.Parcel;
-import android.provider.SimPhonebookContract.SimRecords.NameValidationResult;
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
@@ -71,50 +65,5 @@
SimPhonebookContract.ElementaryFiles.EF_ADN, -1)
);
}
-
- @Test
- public void nameValidationResult_isValid_validNames() {
- assertThat(new NameValidationResult("", "", 0, 1).isValid()).isTrue();
- assertThat(new NameValidationResult("a", "a", 1, 1).isValid()).isTrue();
- assertThat(new NameValidationResult("First Last", "First Last", 10, 10).isValid()).isTrue();
- assertThat(
- new NameValidationResult("First Last", "First Last", 10, 100).isValid()).isTrue();
- }
-
- @Test
- public void nameValidationResult_isValid_invalidNames() {
- assertThat(new NameValidationResult("", "", 0, 0).isValid()).isFalse();
- assertThat(new NameValidationResult("ab", "ab", 2, 1).isValid()).isFalse();
- NameValidationResult unsupportedCharactersResult = new NameValidationResult("A_b_c",
- "A b c", 5, 5);
- assertThat(unsupportedCharactersResult.isValid()).isFalse();
- assertThat(unsupportedCharactersResult.isSupportedCharacter(0)).isTrue();
- assertThat(unsupportedCharactersResult.isSupportedCharacter(1)).isFalse();
- assertThat(unsupportedCharactersResult.isSupportedCharacter(2)).isTrue();
- assertThat(unsupportedCharactersResult.isSupportedCharacter(3)).isFalse();
- assertThat(unsupportedCharactersResult.isSupportedCharacter(4)).isTrue();
- }
-
- @Test
- public void nameValidationResult_parcel() {
- ContentValues values = new ContentValues();
- values.put("name", "Name");
- values.put("phone_number", "123");
-
- NameValidationResult result;
- Parcel parcel = Parcel.obtain();
- try {
- parcel.writeParcelable(new NameValidationResult("name", "sanitized name", 1, 2), 0);
- parcel.setDataPosition(0);
- result = parcel.readParcelable(NameValidationResult.class.getClassLoader());
- } finally {
- parcel.recycle();
- }
-
- assertThat(result.getName()).isEqualTo("name");
- assertThat(result.getSanitizedName()).isEqualTo("sanitized name");
- assertThat(result.getEncodedLength()).isEqualTo(1);
- assertThat(result.getMaxEncodedLength()).isEqualTo(2);
- }
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index fe4d0cf..8fd5d80 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -478,6 +478,8 @@
<permission name="android.permission.CAPTURE_AUDIO_HOTWORD" />
<!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
<permission name="android.permission.SIGNAL_REBOOT_READINESS" />
+ <!-- Permission required for CTS test - PeopleManagerTest -->
+ <permission name="android.permission.READ_PEOPLE_DATA" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 2b0d7e5..c79c12c 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -279,8 +279,8 @@
* }
*/
public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs {
-
- private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake");
+ private static final X500Principal DEFAULT_CERT_SUBJECT =
+ new X500Principal("CN=Android Keystore Key");
private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1");
private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970
private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
@@ -317,6 +317,7 @@
private final boolean mUnlockedDeviceRequired;
private final boolean mCriticalToDeviceEncryption;
private final int mMaxUsageCount;
+ private final String mAttestKeyAlias;
/*
* ***NOTE***: All new fields MUST also be added to the following:
* ParcelableKeyGenParameterSpec class.
@@ -358,7 +359,8 @@
boolean userConfirmationRequired,
boolean unlockedDeviceRequired,
boolean criticalToDeviceEncryption,
- int maxUsageCount) {
+ int maxUsageCount,
+ String attestKeyAlias) {
if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
}
@@ -413,6 +415,7 @@
mUnlockedDeviceRequired = unlockedDeviceRequired;
mCriticalToDeviceEncryption = criticalToDeviceEncryption;
mMaxUsageCount = maxUsageCount;
+ mAttestKeyAlias = attestKeyAlias;
}
/**
@@ -869,6 +872,18 @@
}
/**
+ * Returns the alias of the attestation key that will be used to sign the attestation
+ * certificate of the generated key. Note that an attestation certificate will only be
+ * generated if an attestation challenge is set.
+ *
+ * @see Builder#setAttestKeyAlias(String)
+ */
+ @Nullable
+ public String getAttestKeyAlias() {
+ return mAttestKeyAlias;
+ }
+
+ /**
* Builder of {@link KeyGenParameterSpec} instances.
*/
public final static class Builder {
@@ -906,6 +921,7 @@
private boolean mUnlockedDeviceRequired = false;
private boolean mCriticalToDeviceEncryption = false;
private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
+ private String mAttestKeyAlias = null;
/**
* Creates a new instance of the {@code Builder}.
@@ -975,6 +991,7 @@
mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
mMaxUsageCount = sourceSpec.getMaxUsageCount();
+ mAttestKeyAlias = sourceSpec.getAttestKeyAlias();
}
/**
@@ -1695,6 +1712,28 @@
}
/**
+ * Sets the alias of the attestation key that will be used to sign the attestation
+ * certificate for the generated key pair, if an attestation challenge is set with {@link
+ * #setAttestationChallenge}. If an attestKeyAlias is set but no challenge, {@link
+ * java.security.KeyPairGenerator#initialize} will throw {@link
+ * java.security.InvalidAlgorithmParameterException}.
+ *
+ * <p>If the attestKeyAlias is set to null (the default), Android Keystore will select an
+ * appropriate system-provided attestation signing key. If not null, the alias must
+ * reference an Android Keystore Key that was created with {@link
+ * android.security.keystore.KeyProperties#PURPOSE_ATTEST_KEY}, or key generation will throw
+ * {@link java.security.InvalidAlgorithmParameterException}.
+ *
+ * @param attestKeyAlias the alias of the attestation key to be used to sign the
+ * attestation certificate.
+ */
+ @NonNull
+ public Builder setAttestKeyAlias(@Nullable String attestKeyAlias) {
+ mAttestKeyAlias = attestKeyAlias;
+ return this;
+ }
+
+ /**
* Builds an instance of {@code KeyGenParameterSpec}.
*/
@NonNull
@@ -1731,7 +1770,8 @@
mUserConfirmationRequired,
mUnlockedDeviceRequired,
mCriticalToDeviceEncryption,
- mMaxUsageCount);
+ mMaxUsageCount,
+ mAttestKeyAlias);
}
}
}
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 293ab05..7b0fa91 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -70,6 +70,7 @@
PURPOSE_VERIFY,
PURPOSE_WRAP_KEY,
PURPOSE_AGREE_KEY,
+ PURPOSE_ATTEST_KEY,
})
public @interface PurposeEnum {}
@@ -113,6 +114,13 @@
public static final int PURPOSE_AGREE_KEY = 1 << 6;
/**
+ * Purpose of key: Signing attestaions. This purpose is incompatible with all others, meaning
+ * that when generating a key with PURPOSE_ATTEST_KEY, no other purposes may be specified. In
+ * addition, PURPOSE_ATTEST_KEY may not be specified for imported keys.
+ */
+ public static final int PURPOSE_ATTEST_KEY = 1 << 7;
+
+ /**
* @hide
*/
public static abstract class Purpose {
@@ -132,6 +140,8 @@
return KeymasterDefs.KM_PURPOSE_WRAP;
case PURPOSE_AGREE_KEY:
return KeymasterDefs.KM_PURPOSE_AGREE_KEY;
+ case PURPOSE_ATTEST_KEY:
+ return KeymasterDefs.KM_PURPOSE_ATTEST_KEY;
default:
throw new IllegalArgumentException("Unknown purpose: " + purpose);
}
@@ -151,6 +161,8 @@
return PURPOSE_WRAP_KEY;
case KeymasterDefs.KM_PURPOSE_AGREE_KEY:
return PURPOSE_AGREE_KEY;
+ case KeymasterDefs.KM_PURPOSE_ATTEST_KEY:
+ return PURPOSE_ATTEST_KEY;
default:
throw new IllegalArgumentException("Unknown purpose: " + purpose);
}
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index aaa3715..fe92270 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -588,7 +588,8 @@
private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID;
private boolean mCriticalToDeviceEncryption = false;
private boolean mIsStrongBoxBacked = false;
- private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
+ private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
+ private String mAttestKeyAlias = null;
/**
* Creates a new instance of the {@code Builder}.
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 1f2f853..c20cf01 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -110,6 +110,7 @@
out.writeBoolean(mSpec.isUnlockedDeviceRequired());
out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
out.writeInt(mSpec.getMaxUsageCount());
+ out.writeString(mSpec.getAttestKeyAlias());
}
private static Date readDateOrNull(Parcel in) {
@@ -170,6 +171,7 @@
final boolean unlockedDeviceRequired = in.readBoolean();
final boolean criticalToDeviceEncryption = in.readBoolean();
final int maxUsageCount = in.readInt();
+ final String attestKeyAlias = in.readString();
// The KeyGenParameterSpec is intentionally not constructed using a Builder here:
// The intention is for this class to break if new parameters are added to the
// KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
@@ -205,7 +207,8 @@
userConfirmationRequired,
unlockedDeviceRequired,
criticalToDeviceEncryption,
- maxUsageCount);
+ maxUsageCount,
+ attestKeyAlias);
}
public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 4d27c34..b3bfd6a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,7 +20,9 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.security.keymint.KeyParameter;
+import android.hardware.security.keymint.KeyPurpose;
import android.hardware.security.keymint.SecurityLevel;
+import android.hardware.security.keymint.Tag;
import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore;
@@ -37,9 +39,11 @@
import android.security.keystore.KeymasterUtils;
import android.security.keystore.SecureKeyImportUnavailableException;
import android.security.keystore.StrongBoxUnavailableException;
+import android.system.keystore2.Authorization;
import android.system.keystore2.Domain;
import android.system.keystore2.IKeystoreSecurityLevel;
import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyEntryResponse;
import android.system.keystore2.KeyMetadata;
import android.system.keystore2.ResponseCode;
import android.telephony.TelephonyManager;
@@ -61,6 +65,7 @@
import java.security.spec.ECGenParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -69,6 +74,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.function.Predicate;
/**
* Provides a way to create instances of a KeyPair which will be placed in the
@@ -153,6 +159,7 @@
private int mKeymasterAlgorithm = -1;
private int mKeySizeBits;
private SecureRandom mRng;
+ private KeyDescriptor mAttestKeyDescriptor;
private int[] mKeymasterPurposes;
private int[] mKeymasterBlockModes;
@@ -197,83 +204,9 @@
// Legacy/deprecated spec
KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params;
try {
- KeyGenParameterSpec.Builder specBuilder;
- String specKeyAlgorithm = legacySpec.getKeyType();
- if (specKeyAlgorithm != null) {
- // Spec overrides the generator's default key algorithm
- try {
- keymasterAlgorithm =
- KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
- specKeyAlgorithm);
- } catch (IllegalArgumentException e) {
- throw new InvalidAlgorithmParameterException(
- "Invalid key type in parameters", e);
- }
- }
- switch (keymasterAlgorithm) {
- case KeymasterDefs.KM_ALGORITHM_EC:
- specBuilder = new KeyGenParameterSpec.Builder(
- legacySpec.getKeystoreAlias(),
- KeyProperties.PURPOSE_SIGN
- | KeyProperties.PURPOSE_VERIFY);
- // Authorized to be used with any digest (including no digest).
- // MD5 was never offered for Android Keystore for ECDSA.
- specBuilder.setDigests(
- KeyProperties.DIGEST_NONE,
- KeyProperties.DIGEST_SHA1,
- KeyProperties.DIGEST_SHA224,
- KeyProperties.DIGEST_SHA256,
- KeyProperties.DIGEST_SHA384,
- KeyProperties.DIGEST_SHA512);
- break;
- case KeymasterDefs.KM_ALGORITHM_RSA:
- specBuilder = new KeyGenParameterSpec.Builder(
- legacySpec.getKeystoreAlias(),
- KeyProperties.PURPOSE_ENCRYPT
- | KeyProperties.PURPOSE_DECRYPT
- | KeyProperties.PURPOSE_SIGN
- | KeyProperties.PURPOSE_VERIFY);
- // Authorized to be used with any digest (including no digest).
- specBuilder.setDigests(
- KeyProperties.DIGEST_NONE,
- KeyProperties.DIGEST_MD5,
- KeyProperties.DIGEST_SHA1,
- KeyProperties.DIGEST_SHA224,
- KeyProperties.DIGEST_SHA256,
- KeyProperties.DIGEST_SHA384,
- KeyProperties.DIGEST_SHA512);
- // Authorized to be used with any encryption and signature padding
- // schemes (including no padding).
- specBuilder.setEncryptionPaddings(
- KeyProperties.ENCRYPTION_PADDING_NONE,
- KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
- KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
- specBuilder.setSignaturePaddings(
- KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
- KeyProperties.SIGNATURE_PADDING_RSA_PSS);
- // Disable randomized encryption requirement to support encryption
- // padding NONE above.
- specBuilder.setRandomizedEncryptionRequired(false);
- break;
- default:
- throw new ProviderException(
- "Unsupported algorithm: " + mKeymasterAlgorithm);
- }
-
- if (legacySpec.getKeySize() != -1) {
- specBuilder.setKeySize(legacySpec.getKeySize());
- }
- if (legacySpec.getAlgorithmParameterSpec() != null) {
- specBuilder.setAlgorithmParameterSpec(
- legacySpec.getAlgorithmParameterSpec());
- }
- specBuilder.setCertificateSubject(legacySpec.getSubjectDN());
- specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber());
- specBuilder.setCertificateNotBefore(legacySpec.getStartDate());
- specBuilder.setCertificateNotAfter(legacySpec.getEndDate());
- specBuilder.setUserAuthenticationRequired(false);
-
- spec = specBuilder.build();
+ keymasterAlgorithm = getKeymasterAlgorithmFromLegacy(keymasterAlgorithm,
+ legacySpec);
+ spec = buildKeyGenParameterSpecFromLegacy(legacySpec, keymasterAlgorithm);
} catch (NullPointerException | IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(e);
}
@@ -342,6 +275,10 @@
mJcaKeyAlgorithm = jcaKeyAlgorithm;
mRng = random;
mKeyStore = KeyStore2.getInstance();
+
+ mAttestKeyDescriptor = buildAndCheckAttestKeyDescriptor(spec);
+ checkAttestKeyPurpose(spec);
+
success = true;
} finally {
if (!success) {
@@ -350,6 +287,156 @@
}
}
+ private void checkAttestKeyPurpose(KeyGenParameterSpec spec)
+ throws InvalidAlgorithmParameterException {
+ if ((spec.getPurposes() & KeyProperties.PURPOSE_ATTEST_KEY) != 0
+ && spec.getPurposes() != KeyProperties.PURPOSE_ATTEST_KEY) {
+ throw new InvalidAlgorithmParameterException(
+ "PURPOSE_ATTEST_KEY may not be specified with any other purposes");
+ }
+ }
+
+ private KeyDescriptor buildAndCheckAttestKeyDescriptor(KeyGenParameterSpec spec)
+ throws InvalidAlgorithmParameterException {
+ if (spec.getAttestKeyAlias() != null) {
+ KeyDescriptor attestKeyDescriptor = new KeyDescriptor();
+ attestKeyDescriptor.domain = Domain.APP;
+ attestKeyDescriptor.alias = spec.getAttestKeyAlias();
+ try {
+ KeyEntryResponse attestKey = mKeyStore.getKeyEntry(attestKeyDescriptor);
+ checkAttestKeyChallenge(spec);
+ checkAttestKeyPurpose(attestKey.metadata.authorizations);
+ checkAttestKeySecurityLevel(spec, attestKey);
+ } catch (KeyStoreException e) {
+ throw new InvalidAlgorithmParameterException("Invalid attestKeyAlias", e);
+ }
+ return attestKeyDescriptor;
+ }
+ return null;
+ }
+
+ private void checkAttestKeyChallenge(KeyGenParameterSpec spec)
+ throws InvalidAlgorithmParameterException {
+ if (spec.getAttestationChallenge() == null) {
+ throw new InvalidAlgorithmParameterException(
+ "AttestKey specified but no attestation challenge provided");
+ }
+ }
+
+ private void checkAttestKeyPurpose(Authorization[] keyAuths)
+ throws InvalidAlgorithmParameterException {
+ Predicate<Authorization> isAttestKeyPurpose = x -> x.keyParameter.tag == Tag.PURPOSE
+ && x.keyParameter.value.getKeyPurpose() == KeyPurpose.ATTEST_KEY;
+
+ if (Arrays.stream(keyAuths).noneMatch(isAttestKeyPurpose)) {
+ throw new InvalidAlgorithmParameterException(
+ ("Invalid attestKey, does not have PURPOSE_ATTEST_KEY"));
+ }
+ }
+
+ private void checkAttestKeySecurityLevel(KeyGenParameterSpec spec, KeyEntryResponse key)
+ throws InvalidAlgorithmParameterException {
+ boolean attestKeyInStrongBox = key.metadata.keySecurityLevel == SecurityLevel.STRONGBOX;
+ if (spec.isStrongBoxBacked() != attestKeyInStrongBox) {
+ if (attestKeyInStrongBox) {
+ throw new InvalidAlgorithmParameterException(
+ "Invalid security level: Cannot sign non-StrongBox key with "
+ + "StrongBox attestKey");
+
+ } else {
+ throw new InvalidAlgorithmParameterException(
+ "Invalid security level: Cannot sign StrongBox key with "
+ + "non-StrongBox attestKey");
+ }
+ }
+ }
+
+ private int getKeymasterAlgorithmFromLegacy(int keymasterAlgorithm,
+ KeyPairGeneratorSpec legacySpec) throws InvalidAlgorithmParameterException {
+ String specKeyAlgorithm = legacySpec.getKeyType();
+ if (specKeyAlgorithm != null) {
+ // Spec overrides the generator's default key algorithm
+ try {
+ keymasterAlgorithm =
+ KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
+ specKeyAlgorithm);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidAlgorithmParameterException(
+ "Invalid key type in parameters", e);
+ }
+ }
+ return keymasterAlgorithm;
+ }
+
+ private KeyGenParameterSpec buildKeyGenParameterSpecFromLegacy(KeyPairGeneratorSpec legacySpec,
+ int keymasterAlgorithm) {
+ KeyGenParameterSpec.Builder specBuilder;
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ specBuilder = new KeyGenParameterSpec.Builder(
+ legacySpec.getKeystoreAlias(),
+ KeyProperties.PURPOSE_SIGN
+ | KeyProperties.PURPOSE_VERIFY);
+ // Authorized to be used with any digest (including no digest).
+ // MD5 was never offered for Android Keystore for ECDSA.
+ specBuilder.setDigests(
+ KeyProperties.DIGEST_NONE,
+ KeyProperties.DIGEST_SHA1,
+ KeyProperties.DIGEST_SHA224,
+ KeyProperties.DIGEST_SHA256,
+ KeyProperties.DIGEST_SHA384,
+ KeyProperties.DIGEST_SHA512);
+ break;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ specBuilder = new KeyGenParameterSpec.Builder(
+ legacySpec.getKeystoreAlias(),
+ KeyProperties.PURPOSE_ENCRYPT
+ | KeyProperties.PURPOSE_DECRYPT
+ | KeyProperties.PURPOSE_SIGN
+ | KeyProperties.PURPOSE_VERIFY);
+ // Authorized to be used with any digest (including no digest).
+ specBuilder.setDigests(
+ KeyProperties.DIGEST_NONE,
+ KeyProperties.DIGEST_MD5,
+ KeyProperties.DIGEST_SHA1,
+ KeyProperties.DIGEST_SHA224,
+ KeyProperties.DIGEST_SHA256,
+ KeyProperties.DIGEST_SHA384,
+ KeyProperties.DIGEST_SHA512);
+ // Authorized to be used with any encryption and signature padding
+ // schemes (including no padding).
+ specBuilder.setEncryptionPaddings(
+ KeyProperties.ENCRYPTION_PADDING_NONE,
+ KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
+ KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
+ specBuilder.setSignaturePaddings(
+ KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
+ KeyProperties.SIGNATURE_PADDING_RSA_PSS);
+ // Disable randomized encryption requirement to support encryption
+ // padding NONE above.
+ specBuilder.setRandomizedEncryptionRequired(false);
+ break;
+ default:
+ throw new ProviderException(
+ "Unsupported algorithm: " + mKeymasterAlgorithm);
+ }
+
+ if (legacySpec.getKeySize() != -1) {
+ specBuilder.setKeySize(legacySpec.getKeySize());
+ }
+ if (legacySpec.getAlgorithmParameterSpec() != null) {
+ specBuilder.setAlgorithmParameterSpec(
+ legacySpec.getAlgorithmParameterSpec());
+ }
+ specBuilder.setCertificateSubject(legacySpec.getSubjectDN());
+ specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber());
+ specBuilder.setCertificateNotBefore(legacySpec.getStartDate());
+ specBuilder.setCertificateNotAfter(legacySpec.getEndDate());
+ specBuilder.setUserAuthenticationRequired(false);
+
+ return specBuilder.build();
+ }
+
private void resetAll() {
mEntryAlias = null;
mEntryUid = KeyProperties.NAMESPACE_APPLICATION;
@@ -464,7 +551,7 @@
try {
KeyStoreSecurityLevel iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel);
- KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, null,
+ KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, mAttestKeyDescriptor,
constructKeyGenerationArguments(), flags, additionalEntropy);
AndroidKeyStorePublicKey publicKey =
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index f481228..678b0ad 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -357,12 +357,13 @@
"libharfbuzz_ng",
"liblog",
"libminikin",
- "libnativehelper",
"libz",
"libziparchive",
"libjpeg",
],
+ static_libs: ["libnativehelper_lazy"],
+
target: {
android: {
srcs: [ // sources that depend on android only libraries
@@ -444,6 +445,7 @@
"renderthread/TimeLord.cpp",
"hwui/AnimatedImageDrawable.cpp",
"hwui/Bitmap.cpp",
+ "hwui/BlurDrawLooper.cpp",
"hwui/Canvas.cpp",
"hwui/ImageDecoder.cpp",
"hwui/MinikinSkia.cpp",
@@ -480,6 +482,8 @@
target: {
android: {
+ header_libs: ["libandroid_headers_private" ],
+
srcs: [
"hwui/AnimatedImageThread.cpp",
"pipeline/skia/ATraceMemoryDump.cpp",
@@ -567,6 +571,7 @@
name: "hwui_test_defaults",
defaults: ["hwui_defaults"],
test_suites: ["device-tests"],
+ header_libs: ["libandroid_headers_private"],
target: {
android: {
shared_libs: [
@@ -604,7 +609,6 @@
shared_libs: [
"libmemunreachable",
],
-
srcs: [
"tests/unit/main.cpp",
"tests/unit/ABitmapTests.cpp",
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index 8b20492..ce9b288 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -42,7 +42,7 @@
"GpuCompleted",
};
-static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 19,
+static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 20,
"Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
void FrameInfo::importUiThreadInfo(int64_t* info) {
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index ee7d15a..45a367f 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -55,6 +55,7 @@
QueueBufferDuration,
GpuCompleted,
+ SwapBuffersCompleted,
// Must be the last value!
// Also must be kept in sync with FrameMetrics.java#FRAME_STATS_COUNT
@@ -120,6 +121,10 @@
void markSwapBuffers() { set(FrameInfoIndex::SwapBuffers) = systemTime(SYSTEM_TIME_MONOTONIC); }
+ void markSwapBuffersCompleted() {
+ set(FrameInfoIndex::SwapBuffersCompleted) = systemTime(SYSTEM_TIME_MONOTONIC);
+ }
+
void markFrameCompleted() { set(FrameInfoIndex::FrameCompleted) = systemTime(SYSTEM_TIME_MONOTONIC); }
void addFlag(int frameInfoFlag) {
diff --git a/libs/hwui/FrameMetricsReporter.h b/libs/hwui/FrameMetricsReporter.h
index 75b8038..0643e79 100644
--- a/libs/hwui/FrameMetricsReporter.h
+++ b/libs/hwui/FrameMetricsReporter.h
@@ -16,14 +16,16 @@
#pragma once
+#include <utils/Mutex.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
+#include <ui/FatVector.h>
+
#include "FrameInfo.h"
#include "FrameMetricsObserver.h"
#include <string.h>
-#include <vector>
namespace android {
namespace uirenderer {
@@ -32,9 +34,13 @@
public:
FrameMetricsReporter() {}
- void addObserver(FrameMetricsObserver* observer) { mObservers.push_back(observer); }
+ void addObserver(FrameMetricsObserver* observer) {
+ std::lock_guard lock(mObserversLock);
+ mObservers.push_back(observer);
+ }
bool removeObserver(FrameMetricsObserver* observer) {
+ std::lock_guard lock(mObserversLock);
for (size_t i = 0; i < mObservers.size(); i++) {
if (mObservers[i].get() == observer) {
mObservers.erase(mObservers.begin() + i);
@@ -44,16 +50,28 @@
return false;
}
- bool hasObservers() { return mObservers.size() > 0; }
+ bool hasObservers() {
+ std::lock_guard lock(mObserversLock);
+ return mObservers.size() > 0;
+ }
void reportFrameMetrics(const int64_t* stats) {
- for (size_t i = 0; i < mObservers.size(); i++) {
- mObservers[i]->notify(stats);
+ FatVector<sp<FrameMetricsObserver>, 10> copy;
+ {
+ std::lock_guard lock(mObserversLock);
+ copy.reserve(mObservers.size());
+ for (size_t i = 0; i < mObservers.size(); i++) {
+ copy.push_back(mObservers[i]);
+ }
+ }
+ for (size_t i = 0; i < copy.size(); i++) {
+ copy[i]->notify(stats);
}
}
private:
- std::vector<sp<FrameMetricsObserver> > mObservers;
+ FatVector<sp<FrameMetricsObserver>, 10> mObservers GUARDED_BY(mObserversLock);
+ std::mutex mObserversLock;
};
} // namespace uirenderer
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index ccce403..4a2e30d 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -79,7 +79,9 @@
// and filter it out of the frame profile data
static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync;
-JankTracker::JankTracker(ProfileDataContainer* globalData) {
+JankTracker::JankTracker(ProfileDataContainer* globalData)
+ : mData(globalData->getDataMutex())
+ , mDataMutex(globalData->getDataMutex()) {
mGlobalData = globalData;
nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
nsecs_t sfOffset = DeviceInfo::getCompositorOffset();
@@ -107,6 +109,8 @@
}
void JankTracker::finishFrame(const FrameInfo& frame) {
+ std::lock_guard lock(mDataMutex);
+
// Fast-path for jank-free frames
int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted);
if (mDequeueTimeForgiveness && frame[FrameInfoIndex::DequeueBufferDuration] > 500_us) {
@@ -125,7 +129,11 @@
}
}
- LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64, totalDuration);
+ LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64 " start=%" PRIi64
+ " gpuComplete=%" PRIi64, totalDuration,
+ frame[FrameInfoIndex::IntendedVsync],
+ frame[FrameInfoIndex::GpuCompleted]);
+
mData->reportFrame(totalDuration);
(*mGlobalData)->reportFrame(totalDuration);
@@ -188,6 +196,7 @@
void JankTracker::dumpData(int fd, const ProfileDataDescription* description,
const ProfileData* data) {
+
if (description) {
switch (description->type) {
case JankTrackerType::Generic:
@@ -227,6 +236,7 @@
}
void JankTracker::reset() {
+ std::lock_guard lock(mDataMutex);
mFrames.clear();
mData->reset();
(*mGlobalData)->reset();
@@ -235,6 +245,7 @@
}
void JankTracker::finishGpuDraw(const FrameInfo& frame) {
+ std::lock_guard lock(mDataMutex);
int64_t totalGPUDrawTime = frame.gpuDrawTime();
if (totalGPUDrawTime >= 0) {
mData->reportGPUFrame(totalGPUDrawTime);
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index b3fbbfe..0964553 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -84,12 +84,15 @@
// This is only used if we are in pipelined mode and are using HWC2,
// otherwise it's 0.
nsecs_t mDequeueTimeForgiveness = 0;
- ProfileDataContainer mData;
- ProfileDataContainer* mGlobalData;
+ ProfileDataContainer mData GUARDED_BY(mDataMutex);
+ ProfileDataContainer* mGlobalData GUARDED_BY(mDataMutex);
ProfileDataDescription mDescription;
// Ring buffer large enough for 2 seconds worth of frames
RingBuffer<FrameInfo, 120> mFrames;
+
+ // Mutex to protect acccess to mData and mGlobalData obtained from mGlobalData->getDataMutex
+ std::mutex& mDataMutex;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/ProfileDataContainer.cpp b/libs/hwui/ProfileDataContainer.cpp
index 38e0f0a..41afc0e 100644
--- a/libs/hwui/ProfileDataContainer.cpp
+++ b/libs/hwui/ProfileDataContainer.cpp
@@ -38,6 +38,8 @@
}
void ProfileDataContainer::rotateStorage() {
+ std::lock_guard lock(mJankDataMutex);
+
// If we are mapped we want to stop using the ashmem backend and switch to malloc
// We are expecting a switchStorageToAshmem call to follow this, but it's not guaranteed
// If we aren't sitting on top of ashmem then just do a reset() as it's functionally
@@ -50,6 +52,7 @@
}
void ProfileDataContainer::switchStorageToAshmem(int ashmemfd) {
+ std::lock_guard lock(mJankDataMutex);
int regionSize = ashmem_get_size_region(ashmemfd);
if (regionSize < 0) {
int err = errno;
@@ -70,7 +73,9 @@
return;
}
- newData->mergeWith(*mData);
+ if (mData != nullptr) {
+ newData->mergeWith(*mData);
+ }
freeData();
mData = newData;
mIsMapped = true;
diff --git a/libs/hwui/ProfileDataContainer.h b/libs/hwui/ProfileDataContainer.h
index a398694..a61b8dc 100644
--- a/libs/hwui/ProfileDataContainer.h
+++ b/libs/hwui/ProfileDataContainer.h
@@ -19,6 +19,9 @@
#include "ProfileData.h"
#include "utils/Macros.h"
+#include <mutex>
+#include <utils/Mutex.h>
+
namespace android {
namespace uirenderer {
@@ -26,7 +29,8 @@
PREVENT_COPY_AND_ASSIGN(ProfileDataContainer);
public:
- explicit ProfileDataContainer() {}
+ explicit ProfileDataContainer(std::mutex& jankDataMutex)
+ : mData(new ProfileData()), mJankDataMutex(jankDataMutex) {}
~ProfileDataContainer() { freeData(); }
@@ -36,13 +40,16 @@
ProfileData* get() { return mData; }
ProfileData* operator->() { return mData; }
+ std::mutex& getDataMutex() { return mJankDataMutex; }
+
private:
void freeData();
// By default this will use malloc memory. It may be moved later to ashmem
// if there is shared space for it and a request comes in to do that.
- ProfileData* mData = new ProfileData;
+ ProfileData* mData GUARDED_BY(mJankDataMutex);
bool mIsMapped = false;
+ std::mutex& mJankDataMutex;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 8fddf71..1fddac4 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -463,9 +463,7 @@
}
void SkiaCanvas::drawPoint(float x, float y, const Paint& paint) {
- apply_looper(&paint, [&](const SkPaint& p) {
- mCanvas->drawPoint(x, y, p);
- });
+ apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawPoint(x, y, p); });
}
void SkiaCanvas::drawPoints(const float* points, int count, const Paint& paint) {
@@ -493,9 +491,7 @@
void SkiaCanvas::drawRegion(const SkRegion& region, const Paint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
- apply_looper(&paint, [&](const SkPaint& p) {
- mCanvas->drawRegion(region, p);
- });
+ apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawRegion(region, p); });
}
void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
@@ -509,24 +505,18 @@
void SkiaCanvas::drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner,
const Paint& paint) {
- apply_looper(&paint, [&](const SkPaint& p) {
- mCanvas->drawDRRect(outer, inner, p);
- });
+ apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawDRRect(outer, inner, p); });
}
void SkiaCanvas::drawCircle(float x, float y, float radius, const Paint& paint) {
if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
- apply_looper(&paint, [&](const SkPaint& p) {
- mCanvas->drawCircle(x, y, radius, p);
- });
+ apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawCircle(x, y, radius, p); });
}
void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const Paint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
- apply_looper(&paint, [&](const SkPaint& p) {
- mCanvas->drawOval(oval, p);
- });
+ apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawOval(oval, p); });
}
void SkiaCanvas::drawArc(float left, float top, float right, float bottom, float startAngle,
@@ -547,9 +537,7 @@
if (CC_UNLIKELY(path.isEmpty() && (!path.isInverseFillType()))) {
return;
}
- apply_looper(&paint, [&](const SkPaint& p) {
- mCanvas->drawPath(path, p);
- });
+ apply_looper(&paint, [&](const SkPaint& p) { mCanvas->drawPath(path, p); });
}
void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const Paint& paint) {
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 155f6df..eac3f22 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -208,29 +208,21 @@
*/
PaintCoW&& filterPaint(PaintCoW&& paint) const;
+ // proc(const SkPaint& modifiedPaint)
template <typename Proc> void apply_looper(const Paint* paint, Proc proc) {
SkPaint skp;
- SkDrawLooper* looper = nullptr;
+ BlurDrawLooper* looper = nullptr;
if (paint) {
skp = *filterPaint(paint);
looper = paint->getLooper();
}
if (looper) {
- SkSTArenaAlloc<256> alloc;
- SkDrawLooper::Context* ctx = looper->makeContext(&alloc);
- if (ctx) {
- SkDrawLooper::Context::Info info;
- for (;;) {
- SkPaint p = skp;
- if (!ctx->next(&info, &p)) {
- break;
- }
- mCanvas->save();
- mCanvas->translate(info.fTranslate.fX, info.fTranslate.fY);
- proc(p);
- mCanvas->restore();
- }
- }
+ looper->apply(skp, [&](SkPoint offset, const SkPaint& modifiedPaint) {
+ mCanvas->save();
+ mCanvas->translate(offset.fX, offset.fY);
+ proc(modifiedPaint);
+ mCanvas->restore();
+ });
} else {
proc(skp);
}
diff --git a/libs/hwui/hwui/BlurDrawLooper.cpp b/libs/hwui/hwui/BlurDrawLooper.cpp
new file mode 100644
index 0000000..27a038d
--- /dev/null
+++ b/libs/hwui/hwui/BlurDrawLooper.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#include "BlurDrawLooper.h"
+#include <SkMaskFilter.h>
+
+namespace android {
+
+BlurDrawLooper::BlurDrawLooper(SkColor4f color, float blurSigma, SkPoint offset)
+ : mColor(color), mBlurSigma(blurSigma), mOffset(offset) {}
+
+BlurDrawLooper::~BlurDrawLooper() = default;
+
+SkPoint BlurDrawLooper::apply(SkPaint* paint) const {
+ paint->setColor(mColor);
+ if (mBlurSigma > 0) {
+ paint->setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, mBlurSigma, true));
+ }
+ return mOffset;
+}
+
+sk_sp<BlurDrawLooper> BlurDrawLooper::Make(SkColor4f color, SkColorSpace* cs, float blurSigma,
+ SkPoint offset) {
+ if (cs) {
+ SkPaint tmp;
+ tmp.setColor(color, cs); // converts color to sRGB
+ color = tmp.getColor4f();
+ }
+ return sk_sp<BlurDrawLooper>(new BlurDrawLooper(color, blurSigma, offset));
+}
+
+} // namespace android
diff --git a/libs/hwui/hwui/BlurDrawLooper.h b/libs/hwui/hwui/BlurDrawLooper.h
new file mode 100644
index 0000000..7e6786f
--- /dev/null
+++ b/libs/hwui/hwui/BlurDrawLooper.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
+#define ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
+
+#include <SkPaint.h>
+#include <SkRefCnt.h>
+
+class SkColorSpace;
+
+namespace android {
+
+class BlurDrawLooper : public SkRefCnt {
+public:
+ static sk_sp<BlurDrawLooper> Make(SkColor4f, SkColorSpace*, float blurSigma, SkPoint offset);
+
+ ~BlurDrawLooper() override;
+
+ // proc(SkPoint offset, const SkPaint& modifiedPaint)
+ template <typename DrawProc>
+ void apply(const SkPaint& paint, DrawProc proc) const {
+ SkPaint p(paint);
+ proc(this->apply(&p), p); // draw the shadow
+ proc({0, 0}, paint); // draw the original (on top)
+ }
+
+private:
+ const SkColor4f mColor;
+ const float mBlurSigma;
+ const SkPoint mOffset;
+
+ SkPoint apply(SkPaint* paint) const;
+
+ BlurDrawLooper(SkColor4f, float, SkPoint);
+};
+
+} // namespace android
+
+#endif // ANDROID_GRAPHICS_BLURDRAWLOOPER_H_
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 05bae5c..d9c9eee 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -17,11 +17,11 @@
#ifndef ANDROID_GRAPHICS_PAINT_H_
#define ANDROID_GRAPHICS_PAINT_H_
+#include "BlurDrawLooper.h"
#include "Typeface.h"
#include <cutils/compiler.h>
-#include <SkDrawLooper.h>
#include <SkFont.h>
#include <SkPaint.h>
#include <string>
@@ -59,8 +59,8 @@
SkFont& getSkFont() { return mFont; }
const SkFont& getSkFont() const { return mFont; }
- SkDrawLooper* getLooper() const { return mLooper.get(); }
- void setLooper(sk_sp<SkDrawLooper> looper) { mLooper = std::move(looper); }
+ BlurDrawLooper* getLooper() const { return mLooper.get(); }
+ void setLooper(sk_sp<BlurDrawLooper> looper) { mLooper = std::move(looper); }
// These shadow the methods on SkPaint, but we need to so we can keep related
// attributes in-sync.
@@ -155,7 +155,7 @@
private:
SkFont mFont;
- sk_sp<SkDrawLooper> mLooper;
+ sk_sp<BlurDrawLooper> mLooper;
float mLetterSpacing = 0;
float mWordSpacing = 0;
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 3c86b28..bcec0fa 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -25,7 +25,6 @@
#include <nativehelper/ScopedUtfChars.h>
#include <nativehelper/ScopedPrimitiveArray.h>
-#include "SkBlurDrawLooper.h"
#include "SkColorFilter.h"
#include "SkFont.h"
#include "SkFontMetrics.h"
@@ -39,6 +38,7 @@
#include "unicode/ushape.h"
#include "utils/Blur.h"
+#include <hwui/BlurDrawLooper.h>
#include <hwui/MinikinSkia.h>
#include <hwui/MinikinUtils.h>
#include <hwui/Paint.h>
@@ -964,13 +964,13 @@
}
else {
SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
- paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy));
+ paint->setLooper(BlurDrawLooper::Make(color, cs.get(), sigma, {dx, dy}));
}
}
static jboolean hasShadowLayer(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- return paint->getLooper() && paint->getLooper()->asABlurShadow(nullptr);
+ return paint->getLooper() != nullptr;
}
static jboolean equalsForTextMeasurement(CRITICAL_JNI_PARAMS_COMMA jlong lPaint, jlong rPaint) {
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index ee7c4d8..b288402 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -194,28 +194,20 @@
return filterPaint(std::move(paint));
}
-static SkDrawLooper* get_looper(const Paint* paint) {
+static BlurDrawLooper* get_looper(const Paint* paint) {
return paint ? paint->getLooper() : nullptr;
}
template <typename Proc>
-void applyLooper(SkDrawLooper* looper, const SkPaint* paint, Proc proc) {
+void applyLooper(BlurDrawLooper* looper, const SkPaint* paint, Proc proc) {
if (looper) {
- SkSTArenaAlloc<256> alloc;
- SkDrawLooper::Context* ctx = looper->makeContext(&alloc);
- if (ctx) {
- SkDrawLooper::Context::Info info;
- for (;;) {
- SkPaint p;
- if (paint) {
- p = *paint;
- }
- if (!ctx->next(&info, &p)) {
- break;
- }
- proc(info.fTranslate.fX, info.fTranslate.fY, &p);
- }
+ SkPaint p;
+ if (paint) {
+ p = *paint;
}
+ looper->apply(p, [&](SkPoint offset, const SkPaint& modifiedPaint) {
+ proc(offset.fX, offset.fY, &modifiedPaint);
+ });
} else {
proc(0, 0, paint);
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 9543d47..b760db2 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -178,12 +178,16 @@
if (surfaceControl == mSurfaceControl) return;
auto funcs = mRenderThread.getASurfaceControlFunctions();
+
if (mSurfaceControl != nullptr) {
+ funcs.unregisterListenerFunc(this, &onSurfaceStatsAvailable);
funcs.releaseFunc(mSurfaceControl);
}
mSurfaceControl = surfaceControl;
+ mExpectSurfaceStats = surfaceControl != nullptr;
if (mSurfaceControl != nullptr) {
funcs.acquireFunc(mSurfaceControl);
+ funcs.registerListenerFunc(surfaceControl, this, &onSurfaceStatsAvailable);
}
}
@@ -332,8 +336,8 @@
// just keep using the previous frame's structure instead
if (!wasSkipped(mCurrentFrameInfo)) {
mCurrentFrameInfo = mJankTracker.startFrame();
- mLast4FrameInfos.next().first = mCurrentFrameInfo;
}
+
mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued;
mCurrentFrameInfo->markSyncStart();
@@ -538,17 +542,14 @@
}
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = swap.dequeueDuration;
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = swap.queueDuration;
- mLast4FrameInfos[-1].second = frameCompleteNr;
mHaveNewSurface = false;
mFrameNumber = -1;
} else {
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = 0;
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = 0;
- mLast4FrameInfos[-1].second = -1;
}
- // TODO: Use a fence for real completion?
- mCurrentFrameInfo->markFrameCompleted();
+ mCurrentFrameInfo->markSwapBuffersCompleted();
#if LOG_FRAMETIME_MMA
float thisFrame = mCurrentFrameInfo->duration(FrameInfoIndex::IssueDrawCommandsStart,
@@ -572,30 +573,73 @@
mFrameCompleteCallbacks.clear();
}
- mJankTracker.finishFrame(*mCurrentFrameInfo);
- if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
- mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data());
- }
-
- if (mLast4FrameInfos.size() == mLast4FrameInfos.capacity()) {
- // By looking 4 frames back, we guarantee all SF stats are available. There are at
- // most 3 buffers in BufferQueue. Surface object keeps stats for the last 8 frames.
- FrameInfo* forthBehind = mLast4FrameInfos.front().first;
- int64_t composedFrameId = mLast4FrameInfos.front().second;
- nsecs_t acquireTime = -1;
- if (mNativeSurface) {
- native_window_get_frame_timestamps(mNativeSurface->getNativeWindow(), composedFrameId,
- nullptr, &acquireTime, nullptr, nullptr, nullptr,
- nullptr, nullptr, nullptr, nullptr);
+ if (requireSwap) {
+ if (mExpectSurfaceStats) {
+ std::lock_guard lock(mLast4FrameInfosMutex);
+ std::pair<FrameInfo*, int64_t>& next = mLast4FrameInfos.next();
+ next.first = mCurrentFrameInfo;
+ next.second = frameCompleteNr;
+ } else {
+ mCurrentFrameInfo->markFrameCompleted();
+ mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted)
+ = mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted);
+ finishFrame(mCurrentFrameInfo);
}
- // Ignore default -1, NATIVE_WINDOW_TIMESTAMP_INVALID and NATIVE_WINDOW_TIMESTAMP_PENDING
- forthBehind->set(FrameInfoIndex::GpuCompleted) = acquireTime > 0 ? acquireTime : -1;
- mJankTracker.finishGpuDraw(*forthBehind);
}
mRenderThread.cacheManager().onFrameCompleted();
}
+void CanvasContext::finishFrame(FrameInfo* frameInfo) {
+
+ // TODO (b/169858044): Consolidate this into a single call.
+ mJankTracker.finishFrame(*frameInfo);
+ mJankTracker.finishGpuDraw(*frameInfo);
+
+ // TODO (b/169858044): Move this into JankTracker to adjust deadline when queue is
+ // double-stuffed.
+ if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
+ mFrameMetricsReporter->reportFrameMetrics(frameInfo->data());
+ }
+}
+
+void CanvasContext::onSurfaceStatsAvailable(void* context, ASurfaceControl* control,
+ ASurfaceControlStats* stats) {
+
+ CanvasContext* instance = static_cast<CanvasContext*>(context);
+
+ const ASurfaceControlFunctions& functions =
+ instance->mRenderThread.getASurfaceControlFunctions();
+
+ nsecs_t gpuCompleteTime = functions.getAcquireTimeFunc(stats);
+ uint64_t frameNumber = functions.getFrameNumberFunc(stats);
+
+ FrameInfo* frameInfo = nullptr;
+ {
+ std::lock_guard(instance->mLast4FrameInfosMutex);
+ for (size_t i = 0; i < instance->mLast4FrameInfos.size(); i++) {
+ if (instance->mLast4FrameInfos[i].second == frameNumber) {
+ frameInfo = instance->mLast4FrameInfos[i].first;
+ break;
+ }
+ }
+ }
+ if (frameInfo != nullptr) {
+ if (gpuCompleteTime == -1) {
+ gpuCompleteTime = frameInfo->get(FrameInfoIndex::SwapBuffersCompleted);
+ }
+ if (gpuCompleteTime < frameInfo->get(FrameInfoIndex::SwapBuffers)) {
+ // TODO (b/180488606): Investigate why this can happen for first frames.
+ ALOGW("Impossible GPU complete time swapBuffers=%" PRIi64 " gpuComplete=%" PRIi64,
+ frameInfo->get(FrameInfoIndex::SwapBuffers), gpuCompleteTime);
+ gpuCompleteTime = frameInfo->get(FrameInfoIndex::SwapBuffersCompleted);
+ }
+ frameInfo->set(FrameInfoIndex::FrameCompleted) = gpuCompleteTime;
+ frameInfo->set(FrameInfoIndex::GpuCompleted) = gpuCompleteTime;
+ instance->finishFrame(frameInfo);
+ }
+}
+
// Called by choreographer to do an RT-driven animation
void CanvasContext::doFrame() {
if (!mRenderPipeline->isSurfaceReady()) return;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 917b00c..2e7b2f6 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -37,6 +37,7 @@
#include <SkSize.h>
#include <cutils/compiler.h>
#include <utils/Functor.h>
+#include <utils/Mutex.h>
#include <functional>
#include <future>
@@ -196,6 +197,10 @@
SkISize getNextFrameSize() const;
+ // Called when SurfaceStats are available.
+ static void onSurfaceStatsAvailable(void* context, ASurfaceControl* control,
+ ASurfaceControlStats* stats);
+
private:
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline);
@@ -212,6 +217,7 @@
void setupPipelineSurface();
SkRect computeDirtyRect(const Frame& frame, SkRect* dirty);
+ void finishFrame(FrameInfo* frameInfo);
// The same type as Frame.mWidth and Frame.mHeight
int32_t mLastFrameWidth = 0;
@@ -261,7 +267,12 @@
std::vector<sp<RenderNode>> mRenderNodes;
FrameInfo* mCurrentFrameInfo = nullptr;
- RingBuffer<std::pair<FrameInfo*, int64_t>, 4> mLast4FrameInfos;
+
+ // List of frames that are awaiting GPU completion reporting
+ RingBuffer<std::pair<FrameInfo*, int64_t>, 4> mLast4FrameInfos
+ GUARDED_BY(mLast4FrameInfosMutex);
+ std::mutex mLast4FrameInfosMutex;
+
std::string mName;
JankTracker mJankTracker;
FrameInfoVisualizer mProfiler;
@@ -276,6 +287,9 @@
std::unique_ptr<IRenderPipeline> mRenderPipeline;
std::vector<std::function<void(int64_t)>> mFrameCompleteCallbacks;
+
+ // If set to true, we expect that callbacks into onSurfaceStatsAvailable
+ bool mExpectSurfaceStats = false;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index e14842f..b9568fc 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -215,6 +215,7 @@
void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
mRenderThread.queue().runSync([&]() {
+ std::lock_guard lock(mRenderThread.getJankDataMutex());
mContext->profiler().dumpData(fd);
if (dumpFlags & DumpFlags::FrameStats) {
mContext->dumpFrames(fd);
@@ -234,6 +235,7 @@
uint32_t RenderProxy::frameTimePercentile(int percentile) {
return mRenderThread.queue().runSync([&]() -> auto {
+ std::lock_guard lock(mRenderThread.globalProfileData().getDataMutex());
return mRenderThread.globalProfileData()->findPercentile(percentile);
});
}
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 2610186..5dc02e8 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -59,6 +59,26 @@
releaseFunc = (ASC_release) dlsym(handle_, "ASurfaceControl_release");
LOG_ALWAYS_FATAL_IF(releaseFunc == nullptr,
"Failed to find required symbol ASurfaceControl_release!");
+
+ registerListenerFunc = (ASC_registerSurfaceStatsListener) dlsym(handle_,
+ "ASurfaceControl_registerSurfaceStatsListener");
+ LOG_ALWAYS_FATAL_IF(registerListenerFunc == nullptr,
+ "Failed to find required symbol ASurfaceControl_registerSurfaceStatsListener!");
+
+ unregisterListenerFunc = (ASC_unregisterSurfaceStatsListener) dlsym(handle_,
+ "ASurfaceControl_unregisterSurfaceStatsListener");
+ LOG_ALWAYS_FATAL_IF(unregisterListenerFunc == nullptr,
+ "Failed to find required symbol ASurfaceControl_unregisterSurfaceStatsListener!");
+
+ getAcquireTimeFunc = (ASCStats_getAcquireTime) dlsym(handle_,
+ "ASurfaceControlStats_getAcquireTime");
+ LOG_ALWAYS_FATAL_IF(getAcquireTimeFunc == nullptr,
+ "Failed to find required symbol ASurfaceControlStats_getAcquireTime!");
+
+ getFrameNumberFunc = (ASCStats_getFrameNumber) dlsym(handle_,
+ "ASurfaceControlStats_getFrameNumber");
+ LOG_ALWAYS_FATAL_IF(getFrameNumberFunc == nullptr,
+ "Failed to find required symbol ASurfaceControlStats_getFrameNumber!");
}
void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
@@ -146,7 +166,8 @@
, mFrameCallbackTaskPending(false)
, mRenderState(nullptr)
, mEglManager(nullptr)
- , mFunctorManager(WebViewFunctorManager::instance()) {
+ , mFunctorManager(WebViewFunctorManager::instance())
+ , mGlobalProfileData(mJankDataMutex) {
Properties::load();
start("RenderThread");
}
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index bb7c518..a7d1ba8 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -17,6 +17,7 @@
#ifndef RENDERTHREAD_H_
#define RENDERTHREAD_H_
+#include <surface_control_private.h>
#include <GrDirectContext.h>
#include <SkBitmap.h>
#include <cutils/compiler.h>
@@ -81,11 +82,22 @@
typedef void (*ASC_acquire)(ASurfaceControl* control);
typedef void (*ASC_release)(ASurfaceControl* control);
+typedef void (*ASC_registerSurfaceStatsListener)(ASurfaceControl* control, void* context,
+ ASurfaceControl_SurfaceStatsListener func);
+typedef void (*ASC_unregisterSurfaceStatsListener)(void* context,
+ ASurfaceControl_SurfaceStatsListener func);
+
+typedef int64_t (*ASCStats_getAcquireTime)(ASurfaceControlStats* stats);
+typedef uint64_t (*ASCStats_getFrameNumber)(ASurfaceControlStats* stats);
+
struct ASurfaceControlFunctions {
ASurfaceControlFunctions();
-
ASC_acquire acquireFunc;
ASC_release releaseFunc;
+ ASC_registerSurfaceStatsListener registerListenerFunc;
+ ASC_unregisterSurfaceStatsListener unregisterListenerFunc;
+ ASCStats_getAcquireTime getAcquireTimeFunc;
+ ASCStats_getFrameNumber getFrameNumberFunc;
};
class ChoreographerSource;
@@ -114,6 +126,7 @@
RenderState& renderState() const { return *mRenderState; }
EglManager& eglManager() const { return *mEglManager; }
ProfileDataContainer& globalProfileData() { return mGlobalProfileData; }
+ std::mutex& getJankDataMutex() { return mJankDataMutex; }
Readback& readback();
GrDirectContext* getGrContext() const { return mGrContext.get(); }
@@ -205,6 +218,7 @@
sp<VulkanManager> mVkManager;
ASurfaceControlFunctions mASurfaceControlFunctions;
+ std::mutex mJankDataMutex;
};
} /* namespace renderthread */
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index 7951537..a1ba70a 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -16,7 +16,6 @@
#include "tests/common/TestUtils.h"
-#include <SkBlurDrawLooper.h>
#include <SkColorMatrixFilter.h>
#include <SkColorSpace.h>
#include <SkImagePriv.h>
@@ -85,15 +84,3 @@
ASSERT_EQ(sRGB1.get(), sRGB2.get());
}
-TEST(SkiaBehavior, blurDrawLooper) {
- sk_sp<SkDrawLooper> looper = SkBlurDrawLooper::Make(SK_ColorRED, 5.0f, 3.0f, 4.0f);
-
- SkDrawLooper::BlurShadowRec blur;
- bool success = looper->asABlurShadow(&blur);
- ASSERT_TRUE(success);
-
- ASSERT_EQ(SK_ColorRED, blur.fColor);
- ASSERT_EQ(5.0f, blur.fSigma);
- ASSERT_EQ(3.0f, blur.fOffset.fX);
- ASSERT_EQ(4.0f, blur.fOffset.fY);
-}
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index f77ca2a..dae3c94 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -17,7 +17,6 @@
#include "tests/common/TestUtils.h"
#include <hwui/Paint.h>
-#include <SkBlurDrawLooper.h>
#include <SkCanvasStateUtils.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>
@@ -37,7 +36,7 @@
// it is transparent to ensure that we still draw the rect since it has a looper
paint.setColor(SK_ColorTRANSPARENT);
// this is how view's shadow layers are implemented
- paint.setLooper(SkBlurDrawLooper::Make(0xF0000000, 6.0f, 0, 10));
+ paint.setLooper(BlurDrawLooper::Make({0, 0, 0, 240.0f / 255}, nullptr, 6.0f, {0, 10}));
canvas.drawRect(3, 3, 7, 7, paint);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index e2fdf2f..09c6a4f 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -20,7 +20,6 @@
#include <utils/Blur.h>
#include <SkColorFilter.h>
-#include <SkDrawLooper.h>
#include <SkPaint.h>
#include <SkShader.h>
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 49f9d66..adb8a54c 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -38,6 +38,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -2493,4 +2494,80 @@
return mPlaybackId;
}
}
+
+ /**
+ * Returns recent {@link LogMessage LogMessages} associated with this {@link MediaDrm}
+ * instance.
+ */
+ @NonNull
+ public native List<LogMessage> getLogMessages();
+
+ /**
+ * A {@link LogMessage} records an event in the {@link MediaDrm} framework
+ * or vendor plugin.
+ */
+ public static class LogMessage {
+
+ /**
+ * Timing of the recorded event measured in milliseconds since the Epoch,
+ * 1970-01-01 00:00:00 +0000 (UTC).
+ */
+ public final long timestampMillis;
+
+ /**
+ * Priority of the recorded event.
+ * <p>
+ * Possible priority constants are defined in {@link Log}, e.g.:
+ * <ul>
+ * <li>{@link Log#ASSERT}</li>
+ * <li>{@link Log#ERROR}</li>
+ * <li>{@link Log#WARN}</li>
+ * <li>{@link Log#INFO}</li>
+ * <li>{@link Log#DEBUG}</li>
+ * <li>{@link Log#VERBOSE}</li>
+ * </ul>
+ */
+ @Log.Level
+ public final int priority;
+
+ /**
+ * Description of the recorded event.
+ */
+ @NonNull
+ public final String message;
+
+ private LogMessage(long timestampMillis, int priority, String message) {
+ this.timestampMillis = timestampMillis;
+ if (priority < Log.VERBOSE || priority > Log.ASSERT) {
+ throw new IllegalArgumentException("invalid log priority " + priority);
+ }
+ this.priority = priority;
+ this.message = message;
+ }
+
+ private char logPriorityChar() {
+ switch (priority) {
+ case Log.VERBOSE:
+ return 'V';
+ case Log.DEBUG:
+ return 'D';
+ case Log.INFO:
+ return 'I';
+ case Log.WARN:
+ return 'W';
+ case Log.ERROR:
+ return 'E';
+ case Log.ASSERT:
+ return 'F';
+ default:
+ }
+ return 'U';
+ }
+
+ @Override
+ public String toString() {
+ return String.format("LogMessage{%s %c %s}",
+ Instant.ofEpochMilli(timestampMillis), logPriorityChar(), message);
+ }
+ }
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 2c45ed3..c51c9dd 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -921,7 +921,7 @@
final AudioAttributes aa = audioAttributes != null ? audioAttributes :
new AudioAttributes.Builder().build();
mp.setAudioAttributes(aa);
- mp.setAudioSessionId(audioSessionId);
+ mp.native_setAudioSessionId(audioSessionId);
mp.setDataSource(context, uri);
if (holder != null) {
mp.setDisplay(holder);
@@ -987,7 +987,7 @@
final AudioAttributes aa = audioAttributes != null ? audioAttributes :
new AudioAttributes.Builder().build();
mp.setAudioAttributes(aa);
- mp.setAudioSessionId(audioSessionId);
+ mp.native_setAudioSessionId(audioSessionId);
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
afd.close();
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 4972529..6160b81 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -64,6 +64,7 @@
"android.hardware.cas@1.0",
"android.hardware.cas.native@1.0",
"android.hardware.drm@1.3",
+ "android.hardware.drm@1.4",
"android.hidl.memory@1.0",
"android.hidl.token@1.0-utils",
],
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 0e8719e..22c3572 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -37,6 +37,7 @@
#include <mediadrm/DrmUtils.h>
#include <mediadrm/IDrmMetricsConsumer.h>
#include <mediadrm/IDrm.h>
+#include <utils/Vector.h>
using ::android::os::PersistableBundle;
namespace drm = ::android::hardware::drm;
@@ -187,6 +188,11 @@
jclass classId;
};
+struct LogMessageFields {
+ jmethodID init;
+ jclass classId;
+};
+
struct fields_t {
jfieldID context;
jmethodID post_event;
@@ -208,6 +214,7 @@
jmethodID createFromParcelId;
jclass parcelCreatorClassId;
KeyStatusFields keyStatus;
+ LogMessageFields logMessage;
};
static fields_t gFields;
@@ -224,6 +231,19 @@
return result;
}
+jobject hidlLogMessagesToJavaList(JNIEnv *env, const Vector<drm::V1_4::LogMessage> &logs) {
+ jclass clazz = gFields.arraylistClassId;
+ jobject arrayList = env->NewObject(clazz, gFields.arraylist.init);
+ clazz = gFields.logMessage.classId;
+ for (auto log: logs) {
+ jobject jLog = env->NewObject(clazz, gFields.logMessage.init,
+ static_cast<jlong>(log.timeMs),
+ static_cast<jint>(log.priority),
+ env->NewStringUTF(log.message.c_str()));
+ env->CallBooleanMethod(arrayList, gFields.arraylist.add, jLog);
+ }
+ return arrayList;
+}
} // namespace anonymous
// ----------------------------------------------------------------------------
@@ -907,6 +927,10 @@
FIND_CLASS(clazz, "android/media/MediaDrm$KeyStatus");
gFields.keyStatus.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
GET_METHOD_ID(gFields.keyStatus.init, clazz, "<init>", "([BI)V");
+
+ FIND_CLASS(clazz, "android/media/MediaDrm$LogMessage");
+ gFields.logMessage.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
+ GET_METHOD_ID(gFields.logMessage.init, clazz, "<init>", "(JILjava/lang/String;)V");
}
static void android_media_MediaDrm_native_setup(
@@ -1996,6 +2020,22 @@
throwExceptionAsNecessary(env, err, "Failed to set playbackId");
}
+static jobject android_media_MediaDrm_getLogMessages(
+ JNIEnv *env, jobject thiz) {
+ sp<IDrm> drm = GetDrm(env, thiz);
+ if (!CheckDrm(env, drm)) {
+ return NULL;
+ }
+
+ Vector<drm::V1_4::LogMessage> logs;
+ status_t err = drm->getLogMessages(logs);
+ ALOGI("drm->getLogMessages %zu logs", logs.size());
+ if (throwExceptionAsNecessary(env, err, "Failed to get log messages")) {
+ return NULL;
+ }
+ return hidlLogMessagesToJavaList(env, logs);
+}
+
static const JNINativeMethod gMethods[] = {
{ "native_release", "()V", (void *)android_media_MediaDrm_native_release },
@@ -2123,6 +2163,9 @@
{ "setPlaybackId", "([BLjava/lang/String;)V",
(void *)android_media_MediaDrm_setPlaybackId },
+
+ { "getLogMessages", "()Ljava/util/List;",
+ (void *)android_media_MediaDrm_getLogMessages },
};
int register_android_media_Drm(JNIEnv *env) {
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 3daaf05..253ef67 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -88,7 +88,7 @@
"libarect",
],
- header_libs: [ "libhwui_internal_headers",],
+ header_libs: [ "libhwui_internal_headers", "libandroid_headers_private"],
whole_static_libs: ["libnativewindow"],
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 7a18bd5..b01878b 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -300,3 +300,13 @@
local:
*;
};
+
+LIBANDROID_PLATFORM {
+ global:
+ extern "C++" {
+ ASurfaceControl_registerSurfaceStatsListener*;
+ ASurfaceControl_unregisterSurfaceStatsListener*;
+ ASurfaceControlStats_getAcquireTime*;
+ ASurfaceControlStats_getFrameNumber*;
+ };
+} LIBANDROID;
\ No newline at end of file
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index c1b5f1d..e51add2 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -17,6 +17,7 @@
#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
#include <android/native_window.h>
#include <android/surface_control.h>
+#include <surface_control_private.h>
#include <configstore/Utils.h>
@@ -197,6 +198,48 @@
SurfaceControl_release(surfaceControl);
}
+struct ASurfaceControlStats {
+ int64_t acquireTime;
+ sp<Fence> previousReleaseFence;
+ uint64_t frameNumber;
+};
+
+void ASurfaceControl_registerSurfaceStatsListener(ASurfaceControl* control, void* context,
+ ASurfaceControl_SurfaceStatsListener func) {
+ SurfaceStatsCallback callback = [func](void* callback_context,
+ nsecs_t,
+ const sp<Fence>&,
+ const SurfaceStats& surfaceStats) {
+
+ ASurfaceControlStats aSurfaceControlStats;
+
+ ASurfaceControl* aSurfaceControl =
+ reinterpret_cast<ASurfaceControl*>(surfaceStats.surfaceControl.get());
+ aSurfaceControlStats.acquireTime = surfaceStats.acquireTime;
+ aSurfaceControlStats.previousReleaseFence = surfaceStats.previousReleaseFence;
+ aSurfaceControlStats.frameNumber = surfaceStats.eventStats.frameNumber;
+
+ (*func)(callback_context, aSurfaceControl, &aSurfaceControlStats);
+ };
+ TransactionCompletedListener::getInstance()->addSurfaceStatsListener(context,
+ reinterpret_cast<void*>(func), ASurfaceControl_to_SurfaceControl(control), callback);
+}
+
+
+void ASurfaceControl_unregisterSurfaceStatsListener(void* context,
+ ASurfaceControl_SurfaceStatsListener func) {
+ TransactionCompletedListener::getInstance()->removeSurfaceStatsListener(context,
+ reinterpret_cast<void*>(func));
+}
+
+int64_t ASurfaceControlStats_getAcquireTime(ASurfaceControlStats* stats) {
+ return stats->acquireTime;
+}
+
+uint64_t ASurfaceControlStats_getFrameNumber(ASurfaceControlStats* stats) {
+ return stats->frameNumber;
+}
+
ASurfaceTransaction* ASurfaceTransaction_create() {
Transaction* transaction = new Transaction;
return reinterpret_cast<ASurfaceTransaction*>(transaction);
@@ -215,11 +258,6 @@
transaction->apply();
}
-typedef struct ASurfaceControlStats {
- int64_t acquireTime;
- sp<Fence> previousReleaseFence;
-} ASurfaceControlStats;
-
struct ASurfaceTransactionStats {
std::unordered_map<ASurfaceControl*, ASurfaceControlStats> aSurfaceControlStats;
int64_t latchTime;
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index 92d7bf0..d7c8291 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -456,7 +456,7 @@
* @hide
*/
@SystemApi
- public static final int TETHERING_WIFI = TetheringManager.TETHERING_WIFI;
+ public static final int TETHERING_WIFI = 0;
/**
* USB tethering type.
@@ -464,7 +464,7 @@
* @hide
*/
@SystemApi
- public static final int TETHERING_USB = TetheringManager.TETHERING_USB;
+ public static final int TETHERING_USB = 1;
/**
* Bluetooth tethering type.
@@ -472,7 +472,7 @@
* @hide
*/
@SystemApi
- public static final int TETHERING_BLUETOOTH = TetheringManager.TETHERING_BLUETOOTH;
+ public static final int TETHERING_BLUETOOTH = 2;
/**
* Wifi P2p tethering type.
@@ -2799,7 +2799,7 @@
*/
@SystemApi
@Deprecated
- public static final int TETHER_ERROR_NO_ERROR = TetheringManager.TETHER_ERROR_NO_ERROR;
+ public static final int TETHER_ERROR_NO_ERROR = 0;
/**
* @deprecated Use {@link TetheringManager#TETHER_ERROR_UNKNOWN_IFACE}.
* {@hide}
@@ -2875,8 +2875,7 @@
*/
@SystemApi
@Deprecated
- public static final int TETHER_ERROR_PROVISION_FAILED =
- TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+ public static final int TETHER_ERROR_PROVISION_FAILED = 11;
/**
* @deprecated Use {@link TetheringManager#TETHER_ERROR_DHCPSERVER_ERROR}.
* {@hide}
@@ -2890,8 +2889,7 @@
*/
@SystemApi
@Deprecated
- public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN =
- TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+ public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13;
/**
* Get a more detailed error code after a Tethering or Untethering
diff --git a/packages/Connectivity/framework/src/android/net/TestNetworkManager.java b/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
index 4e89414..a174a7b 100644
--- a/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
+++ b/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
@@ -41,7 +41,6 @@
/**
* Prefix for tap interfaces created by this class.
- * @hide
*/
public static final String TEST_TAP_PREFIX = "testtap";
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 539a81b..a15ceb6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -345,6 +345,12 @@
<!-- Permissions required for CTS test - AdbManagerTest -->
<uses-permission android:name="android.permission.MANAGE_DEBUGGING" />
+ <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+ <uses-permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE" />
+
+ <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+ <uses-permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION" />
+
<!-- Permission needed for CTS test - DisplayTest -->
<uses-permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS" />
@@ -406,6 +412,9 @@
<!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
<uses-permission android:name="android.permission.SIGNAL_REBOOT_READINESS" />
+ <!-- Permission required for CTS test - PeopleManagerTest -->
+ <uses-permission android:name="android.permission.READ_PEOPLE_DATA" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/README.md b/packages/SystemUI/README.md
index ee8d023..60994d8 100644
--- a/packages/SystemUI/README.md
+++ b/packages/SystemUI/README.md
@@ -144,6 +144,10 @@
Delegates SysUI events to WM Shell controllers.
+### [com.android.systemui.people.widget.PeopleSpaceWidgetEnabler](/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java)
+
+Enables People Space widgets.
+
---
* [Plugins](/packages/SystemUI/docs/plugins.md)
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml b/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
similarity index 90%
rename from packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml
rename to packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
index 1f6b24b..dd35dd9 100644
--- a/packages/SystemUI/res-keyguard/drawable/ic_backspace_black_24dp.xml
+++ b/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2017 The Android Open Source Project
+ ~ 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.
@@ -20,6 +20,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="#FF000000"
+ android:fillColor="?android:attr/colorBackground"
android:pathData="M9,15.59L12.59,12L9,8.41L10.41,7L14,10.59L17.59,7L19,8.41L15.41,12L19,15.59L17.59,17L14,13.41L10.41,17L9,15.59zM21,6H8l-4.5,6L8,18h13V6M21,4c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H8c-0.63,0 -1.22,-0.3 -1.6,-0.8L1,12l5.4,-7.2C6.78,4.3 7.37,4 8,4H21L21,4z"/>
</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml b/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml
index b7a9fafd..604ab72 100644
--- a/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml
+++ b/packages/SystemUI/res-keyguard/drawable/num_pad_key_background.xml
@@ -16,8 +16,23 @@
* limitations under the License.
*/
-->
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/colorBackground" />
- <corners android:radius="10dp" />
-</shape>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/background">
+ <shape>
+ <solid android:color="?android:attr/colorControlNormal" />
+ <corners android:radius="10dp" />
+ </shape>
+ </item>
+ <item android:id="@+id/ripple">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorControlNormal" />
+ <corners android:radius="10dp" />
+ </shape>
+ </item>
+ </ripple>
+ </item>
+</layer-list>
+
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index a928b75..2a5784a 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -40,7 +40,7 @@
<dimen name="keyguard_security_view_top_margin">8dp</dimen>
<dimen name="keyguard_security_view_lateral_margin">36dp</dimen>
- <dimen name="keyguard_eca_top_margin">24dp</dimen>
+ <dimen name="keyguard_eca_top_margin">18dp</dimen>
<!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text.
Should be 0 on devices with plenty of room (e.g. tablets) -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index cd82b80..2391803 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -52,9 +52,8 @@
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="NumPadKey.Delete">
- <item name="android:src">@drawable/ic_backspace_black_24dp</item>
- <item name="android:tint">?android:attr/textColorSecondary</item>
- <item name="android:tintMode">src_in</item>
+ <item name="android:colorControlNormal">?android:attr/textColorSecondary</item>
+ <item name="android:src">@drawable/ic_backspace_24dp</item>
</style>
<style name="NumPadKey.Enter">
<item name="android:colorControlNormal">?android:attr/textColorSecondary</item>
diff --git a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml b/packages/SystemUI/res/drawable/circle_green_10dp.xml
similarity index 80%
copy from packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
copy to packages/SystemUI/res/drawable/circle_green_10dp.xml
index 59af775..571ec62 100644
--- a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
+++ b/packages/SystemUI/res/drawable/circle_green_10dp.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 The Android Open Source Project
~
@@ -13,7 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/colorBackground" />
- <corners android:radius="@dimen/people_space_widget_round_radius" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <size android:height="10dp"
+ android:width="10dp" />
+ <solid android:color="#34A853" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/people_space_content_background.xml b/packages/SystemUI/res/drawable/people_space_content_background.xml
index 53108409..32314d2 100644
--- a/packages/SystemUI/res/drawable/people_space_content_background.xml
+++ b/packages/SystemUI/res/drawable/people_space_content_background.xml
@@ -16,5 +16,5 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="?android:attr/colorControlHighlight" />
- <corners android:radius="@dimen/people_space_widget_radius" />
+ <corners android:radius="@dimen/people_space_image_radius" />
</shape>
diff --git a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml b/packages/SystemUI/res/drawable/people_space_new_story_outline.xml
similarity index 87%
rename from packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
rename to packages/SystemUI/res/drawable/people_space_new_story_outline.xml
index 59af775..a1737f9 100644
--- a/packages/SystemUI/res/drawable/people_space_round_tile_view_card.xml
+++ b/packages/SystemUI/res/drawable/people_space_new_story_outline.xml
@@ -13,7 +13,8 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
<solid android:color="?android:attr/colorBackground" />
- <corners android:radius="@dimen/people_space_widget_round_radius" />
+ <stroke android:width="2dp" android:color="?android:attr/colorAccent" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml b/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
index e9f3424..b1c1328 100644
--- a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
@@ -19,70 +19,78 @@
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
- android:background="@drawable/people_space_round_tile_view_card"
+ android:background="@drawable/people_space_tile_view_card"
android:id="@+id/item"
- android:paddingVertical="6dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
- android:gravity="center_vertical"
- android:paddingStart="12dp"
+ android:gravity="center"
+ android:paddingVertical="2dp"
+ android:paddingHorizontal="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
-
+ <LinearLayout
+ android:background="@drawable/people_space_new_story_outline"
+ android:id="@+id/person_icon_with_story"
+ android:gravity="center_horizontal"
+ android:layout_width="60dp"
+ android:layout_height="60dp">
<ImageView
- android:id="@+id/person_icon"
- android:layout_width="60dp"
- android:layout_height="60dp" />
+ android:id="@+id/person_icon_inside_ring"
+ android:layout_marginEnd="4dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="4dp"
+ android:layout_width="52dp"
+ android:layout_height="52dp"/>
+ </LinearLayout>
+ <ImageView
+ android:id="@+id/person_icon_only"
+ android:layout_width="60dp"
+ android:layout_height="60dp"/>
- <LinearLayout
- android:background="@drawable/people_space_rounded_border"
- android:layout_marginStart="-12dp"
- android:layout_marginTop="28dp"
- android:layout_marginBottom="14dp"
- android:layout_width="16dp"
- android:layout_height="16dp">
-
- <ImageView
- android:id="@+id/package_icon"
- android:layout_width="12dp"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
- android:layout_marginBottom="2dp"
- android:layout_marginTop="2dp"
- android:layout_height="12dp" />
- </LinearLayout>
+ <ImageView
+ android:id="@+id/package_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginStart="-20dp"
+ android:layout_marginTop="22dp"/>
<LinearLayout
android:orientation="vertical"
android:paddingStart="8dp"
- android:paddingEnd="12dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
+ <ImageView
+ android:id="@+id/availability"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="@drawable/circle_green_10dp"/>
<TextView
android:id="@+id/name"
+ android:text="@string/empty_user_name"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
- android:textSize="16sp"
+ android:textSize="14sp"
android:maxLines="1"
android:ellipsize="end"
android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"/>
<TextView
- android:id="@+id/status"
+ android:id="@+id/last_interaction"
+ android:text="@string/empty_status"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:paddingVertical="3dp"
android:textSize="12sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="3"
- android:ellipsize="end" />
+ android:ellipsize="end"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/people_space_notification_content_tile.xml b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
new file mode 100644
index 0000000..9ea7aa3
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <RelativeLayout
+ android:background="@drawable/people_space_tile_view_card"
+ android:id="@+id/item"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <include layout="@layout/punctuation_layout"/>
+ <RelativeLayout
+ android:gravity="start"
+ android:id="@+id/column_one"
+ android:paddingVertical="10dp"
+ android:paddingStart="8dp"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+ <TextView
+ android:id="@+id/subtext"
+ android:layout_toStartOf="@+id/content_layout"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:gravity="top|start"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="12sp"
+ android:maxWidth="60dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1"
+ android:ellipsize="end"/>
+ <LinearLayout
+ android:orientation="horizontal"
+ android:id="@+id/avatar_and_app_icon"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentStart="true"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <LinearLayout
+ android:id="@+id/person_icon_with_story"
+ android:background="@drawable/people_space_new_story_outline"
+ android:gravity="center_horizontal"
+ android:layout_width="48dp"
+ android:layout_height="48dp">
+ <ImageView
+ android:id="@+id/person_icon_inside_ring"
+ android:layout_marginEnd="4dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="4dp"
+ android:layout_width="40dp"
+ android:layout_height="40dp"/>
+ </LinearLayout>
+ <ImageView
+ android:id="@+id/person_icon_only"
+ android:layout_width="48dp"
+ android:layout_height="48dp"/>
+ <ImageView
+ android:id="@id/package_icon"
+ android:layout_marginStart="-16dp"
+ android:layout_marginTop="18dp"
+ android:paddingBottom="10dp"
+ android:paddingEnd="8dp"
+ android:layout_width="28dp"
+ android:layout_height="32dp"/>
+ </LinearLayout>
+ </RelativeLayout>
+ <LinearLayout
+ android:id="@+id/content_layout"
+ android:paddingBottom="4dp"
+ android:paddingStart="4dp"
+ android:paddingTop="8dp"
+ android:paddingEnd="8dp"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@id/column_one"
+ android:gravity="top|end"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <TextView
+ android:id="@+id/content"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:gravity="top|end"
+ android:textSize="12sp"
+ android:paddingTop="2dp"
+ android:paddingEnd="4dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:ellipsize="end"/>
+ <LinearLayout
+ android:id="@+id/content_background"
+ android:background="@drawable/people_space_content_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <ImageView
+ android:id="@+id/image"
+ android:adjustViewBounds="true"
+ android:maxHeight="44dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scaleType="centerCrop"/>
+ </LinearLayout>
+ </LinearLayout>
+ <LinearLayout
+ android:id="@+id/person_label"
+ android:paddingBottom="10dp"
+ android:paddingEnd="8dp"
+ android:gravity="start|bottom"
+ android:layout_toEndOf="@id/column_one"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/content_layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <ImageView
+ android:id="@+id/availability"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:paddingVertical="2dp"
+ android:background="@drawable/circle_green_10dp"/>
+ <TextView
+ android:id="@+id/name"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="14sp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+ </RelativeLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
index 03589d3..3300495 100644
--- a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
@@ -14,183 +14,144 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:background="@drawable/people_space_tile_view_card"
+ android:id="@+id/item"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="start">
- <TextView
- android:id="@+id/punctuation1"
- android:textColor="?android:attr/textColorSecondary"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textSize="36sp"
- android:textStyle="bold"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="5dp"
- android:maxLines="1"
- android:alpha="0.2"
- android:rotation="350" />
- <TextView
- android:id="@+id/punctuation2"
- android:textColor="?android:attr/textColorSecondary"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textSize="36sp"
- android:textStyle="bold"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="25dp"
- android:maxLines="1"
- android:alpha="0.2"
- android:rotation="5" />
- <TextView
- android:id="@+id/punctuation3"
- android:textColor="?android:attr/textColorSecondary"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textSize="36sp"
- android:textStyle="bold"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="5dp"
- android:layout_marginStart="25dp"
- android:maxLines="1"
- android:alpha="0.2"
- android:rotation="355"/>
- <TextView
- android:id="@+id/punctuation4"
- android:textColor="?android:attr/textColorSecondary"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textSize="36sp"
- android:textStyle="bold"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="-5dp"
- android:layout_marginStart="25dp"
- android:maxLines="1"
- android:alpha="0.2"
- android:rotation="10" />
- <TextView
- android:id="@+id/punctuation5"
- android:textColor="?android:attr/textColorSecondary"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textSize="36sp"
- android:textStyle="bold"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="5dp"
- android:layout_marginStart="25dp"
- android:maxLines="1"
- android:alpha="0.2"
- android:rotation="15" />
- <TextView
- android:id="@+id/punctuation6"
- android:textColor="?android:attr/textColorSecondary"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textSize="36sp"
- android:textStyle="bold"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="-5dp"
- android:layout_marginStart="25dp"
- android:maxLines="1"
- android:alpha="0.2"
- android:rotation="345" />
- </LinearLayout>
- <LinearLayout
- android:id="@+id/item"
- android:orientation="vertical"
- android:paddingTop="6dp"
- android:layout_width="match_parent"
+ <RelativeLayout
+ android:gravity="start"
+ android:id="@+id/column_one"
+ android:paddingVertical="8dp"
+ android:paddingStart="8dp"
+ android:layout_width="wrap_content"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
- android:paddingHorizontal="12dp"
- android:paddingBottom="4dp"
- android:gravity="top"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <ImageView
- android:id="@+id/person_icon"
- android:layout_width="34dp"
- android:layout_height="34dp" />
-
- <LinearLayout
- android:background="@drawable/people_space_rounded_border"
- android:layout_marginStart="-5dp"
- android:layout_marginTop="18dp"
- android:layout_width="8dp"
- android:layout_height="8dp">
-
- <ImageView
- android:id="@+id/package_icon"
- android:layout_width="6dp"
- android:layout_marginEnd="1dp"
- android:layout_marginStart="1dp"
- android:layout_marginBottom="1dp"
- android:layout_marginTop="1dp"
- android:layout_height="6dp" />
- </LinearLayout>
-
- <LinearLayout
- android:orientation="vertical"
- android:paddingStart="6dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <TextView
- android:id="@+id/name"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp"
- android:maxLines="1"
- android:ellipsize="end"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <TextView
- android:id="@+id/time"
- android:textColor="?android:attr/textColorSecondary"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textSize="10sp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:maxLines="1"
- android:ellipsize="end" />
- </LinearLayout>
- </LinearLayout>
- <LinearLayout
android:id="@+id/content_background"
android:background="@drawable/people_space_content_background"
- android:layout_gravity="center"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <TextView
- android:id="@+id/content"
- android:paddingVertical="3dp"
- android:paddingHorizontal="12dp"
- android:gravity="center"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textSize="16sp"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:maxLines="2"
- android:ellipsize="end" />
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:layout_width="60dp"
+ android:layout_height="60dp">
<ImageView
android:id="@+id/image"
+ android:gravity="center"
+ android:background="@drawable/people_space_content_background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:visibility="gone"
android:scaleType="centerCrop"/>
+ <ImageView
+ android:id="@+id/status_defined_icon"
+ android:gravity="start|top"
+ android:layout_marginStart="-52dp"
+ android:layout_marginTop="8dp"
+ android:layout_width="18dp"
+ android:layout_height="18dp"/>
</LinearLayout>
+ <LinearLayout
+ android:orientation="horizontal"
+ android:id="@+id/avatar_and_app_icon"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentStart="true"
+ android:paddingStart="4dp"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <LinearLayout
+ android:id="@+id/person_icon_with_story"
+ android:background="@drawable/people_space_new_story_outline"
+ android:gravity="center_horizontal"
+ android:layout_width="48dp"
+ android:layout_height="48dp">
+ <ImageView
+ android:id="@+id/person_icon_inside_ring"
+ android:layout_marginEnd="4dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginBottom="4dp"
+ android:layout_marginTop="4dp"
+ android:layout_width="40dp"
+ android:layout_height="40dp"/>
+ </LinearLayout>
+ <ImageView
+ android:id="@+id/person_icon_only"
+ android:layout_width="48dp"
+ android:layout_height="48dp"/>
+ <ImageView
+ android:id="@id/package_icon"
+ android:layout_marginStart="-16dp"
+ android:layout_marginTop="18dp"
+ android:paddingBottom="10dp"
+ android:paddingEnd="8dp"
+ android:layout_width="28dp"
+ android:layout_height="32dp"/>
+ </LinearLayout>
+ </RelativeLayout>
+ <LinearLayout
+ android:id="@+id/content_layout"
+ android:paddingTop="10dp"
+ android:paddingBottom="4dp"
+ android:paddingHorizontal="8dp"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@id/column_one"
+ android:gravity="top|end"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <TextView
+ android:id="@+id/status"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="12sp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:ellipsize="end"/>
+ </LinearLayout>
+ <LinearLayout
+ android:id="@+id/person_label"
+ android:paddingBottom="10dp"
+ android:paddingEnd="8dp"
+ android:gravity="start|bottom"
+ android:layout_toEndOf="@id/column_one"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/content_layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:id="@+id/time"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="12sp"
+ android:paddingVertical="2dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1"
+ android:visibility="gone"
+ android:ellipsize="end"/>
+ <ImageView
+ android:id="@+id/availability"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:paddingVertical="2dp"
+ android:background="@drawable/circle_green_10dp"/>
+ <TextView
+ android:id="@+id/name"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="14sp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/punctuation_layout.xml b/packages/SystemUI/res/layout/punctuation_layout.xml
new file mode 100644
index 0000000..25c7648
--- /dev/null
+++ b/packages/SystemUI/res/layout/punctuation_layout.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/punctuation_layout"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="start">
+ <TextView
+ android:id="@+id/punctuation1"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="36sp"
+ android:textStyle="bold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="5dp"
+ android:maxLines="1"
+ android:alpha="0.2"
+ android:rotation="350"/>
+ <TextView
+ android:id="@+id/punctuation2"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="36sp"
+ android:textStyle="bold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="25dp"
+ android:maxLines="1"
+ android:alpha="0.2"
+ android:rotation="5"/>
+ <TextView
+ android:id="@+id/punctuation3"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="36sp"
+ android:textStyle="bold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="5dp"
+ android:layout_marginStart="25dp"
+ android:maxLines="1"
+ android:alpha="0.2"
+ android:rotation="355"/>
+ <TextView
+ android:id="@+id/punctuation4"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="36sp"
+ android:textStyle="bold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-5dp"
+ android:layout_marginStart="25dp"
+ android:maxLines="1"
+ android:alpha="0.2"
+ android:rotation="10"/>
+ <TextView
+ android:id="@+id/punctuation5"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="36sp"
+ android:textStyle="bold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="5dp"
+ android:layout_marginStart="25dp"
+ android:maxLines="1"
+ android:alpha="0.2"
+ android:rotation="15"/>
+ <TextView
+ android:id="@+id/punctuation6"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="36sp"
+ android:textStyle="bold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-5dp"
+ android:layout_marginStart="25dp"
+ android:maxLines="1"
+ android:alpha="0.2"
+ android:rotation="345"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index dc341274..059bda3 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -54,16 +54,4 @@
android:paddingBottom="10dp"
android:importantForAccessibility="yes" />
- <TextView
- android:id="@+id/header_debug_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:fontFamily="sans-serif-condensed"
- android:padding="2dp"
- android:textColor="#00A040"
- android:textSize="11dp"
- android:textStyle="bold"
- android:visibility="invisible"/>
-
</com.android.systemui.qs.QuickStatusBarHeader>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 78927f8..13c0110 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -312,6 +312,7 @@
<item>com.android.systemui.accessibility.SystemActions</item>
<item>com.android.systemui.toast.ToastUI</item>
<item>com.android.systemui.wmshell.WMShell</item>
+ <item>com.android.systemui.people.widget.PeopleSpaceWidgetEnabler</item>
</string-array>
<!-- QS tile shape store width. negative implies fill configuration instead of stroke-->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0d92aea..b07df9c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1348,8 +1348,8 @@
<dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
<dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
- <dimen name="people_space_widget_radius">24dp</dimen>
- <dimen name="people_space_widget_round_radius">100dp</dimen>
+ <dimen name="people_space_widget_radius">28dp</dimen>
+ <dimen name="people_space_image_radius">20dp</dimen>
<dimen name="people_space_widget_background_padding">6dp</dimen>
<dimen name="rounded_slider_height">48dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d997ca2..3b42600 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -247,6 +247,10 @@
<string name="screenshot_dismiss_description">Dismiss screenshot</string>
<!-- Content description indicating that the view is a preview of the screenshot that was just taken [CHAR LIMIT=NONE] -->
<string name="screenshot_preview_description">Screenshot preview</string>
+ <!-- Content description for the top boundary of the screenshot being cropped [CHAR LIMIT=NONE] -->
+ <string name="screenshot_top_boundary">Top boundary</string>
+ <!-- Content description for the bottom boundary of the screenshot being cropped [CHAR LIMIT=NONE] -->
+ <string name="screenshot_bottom_boundary">Bottom boundary</string>
<!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
<string name="screenrecord_name">Screen Recorder</string>
@@ -1341,6 +1345,9 @@
<!-- Monitoring dialog: Description of Network Logging. [CHAR LIMIT=NONE]-->
<string name="monitoring_description_management_network_logging">Your admin has turned on network logging, which monitors traffic on your device.</string>
+ <!-- Monitoring dialog: Description of Network Logging in the work profile. [CHAR LIMIT=NONE]-->
+ <string name="monitoring_description_managed_profile_network_logging">Your admin has turned on network logging, which monitors traffic in your work profile but not in your personal profile.</string>
+
<!-- Monitoring dialog: Description of an active VPN. [CHAR LIMIT=NONE]-->
<string name="monitoring_description_named_vpn">You\'re connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
@@ -2810,8 +2817,26 @@
<string name="less_than_timestamp" translatable="false">Less than <xliff:g id="duration" example="5 hours">%1$s</xliff:g> ago</string>
<!-- Timestamp for notification when over a certain time window [CHAR LIMIT=120] -->
<string name="over_timestamp" translatable="false">Over <xliff:g id="duration" example="1 week">%1$s</xliff:g> ago</string>
- <!-- Status text for a birthday today [CHAR LIMIT=120] -->
- <string name="birthday_status" translatable="false">Today is their birthday!</string>
+ <!-- Status text for a birthday today [CHAR LIMIT=30] -->
+ <string name="birthday_status" translatable="false">Birthday</string>
+ <!-- Status text for an upcoming birthday [CHAR LIMIT=30] -->
+ <string name="upcoming_birthday_status" translatable="false">Birthday soon</string>
+ <!-- Status text for an anniversary [CHAR LIMIT=30] -->
+ <string name="anniversary_status" translatable="false">Anniversary</string>
+ <!-- Status text for sharing location [CHAR LIMIT=30] -->
+ <string name="location_status" translatable="false">Sharing location</string>
+ <!-- Status text for a new story posted [CHAR LIMIT=30] -->
+ <string name="new_story_status" translatable="false">New story</string>
+ <!-- Status text for watching a video [CHAR LIMIT=30] -->
+ <string name="video_status" translatable="false">Watching</string>
+ <!-- Status text for listening to audio [CHAR LIMIT=30] -->
+ <string name="audio_status" translatable="false">Listening</string>
+ <!-- Status text for playing a game [CHAR LIMIT=30] -->
+ <string name="game_status" translatable="false">Playing</string>
+ <!-- Empty user name before user has selected a friend [CHAR LIMIT=30] -->
+ <string name="empty_user_name" translatable="false">Your friend</string>
+ <!-- Empty status shown before user has selected a friend [CHAR LIMIT=30] -->
+ <string name="empty_status" translatable="false">Their status</string>
<!-- Title to display in a notification when ACTION_BATTERY_CHANGED.EXTRA_PRESENT field is false
[CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml
index d0c63a8..fbdac5e1 100644
--- a/packages/SystemUI/res/xml/people_space_widget_info.xml
+++ b/packages/SystemUI/res/xml/people_space_widget_info.xml
@@ -16,11 +16,11 @@
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="140dp"
- android:minHeight="40dp"
+ android:minHeight="55dp"
android:minResizeWidth="110dp"
- android:minResizeHeight="40dp"
+ android:minResizeHeight="55dp"
android:updatePeriodMillis="60000"
- android:previewImage="@drawable/ic_android"
+ android:previewImage="@drawable/ic_person"
android:resizeMode="horizontal|vertical"
android:configure="com.android.systemui.people.PeopleSpaceActivity"
android:initialLayout="@layout/people_space_widget">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 825ea25..4e06491 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -24,14 +24,12 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import com.android.internal.widget.LockscreenCredential;
-import com.android.settingslib.Utils;
import com.android.systemui.R;
/**
@@ -188,10 +186,6 @@
key.reloadColors();
}
mPasswordEntry.reloadColors();
- int deleteColor = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary)
- .getDefaultColor();
- mDeleteButton.setImageTintList(ColorStateList.valueOf(deleteColor));
-
mDeleteButton.reloadColors();
mOkButton.reloadColors();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index cdf9858..97d6e97 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -17,14 +17,16 @@
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.RippleDrawable;
import android.view.ContextThemeWrapper;
import android.view.ViewGroup;
import androidx.annotation.StyleRes;
-import com.android.internal.graphics.ColorUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -34,13 +36,18 @@
class NumPadAnimator {
private ValueAnimator mAnimator;
private GradientDrawable mBackground;
+ private RippleDrawable mRipple;
+ private GradientDrawable mRippleMask;
private int mMargin;
private int mNormalColor;
private int mHighlightColor;
private int mStyle;
- NumPadAnimator(Context context, final GradientDrawable background, @StyleRes int style) {
- mBackground = (GradientDrawable) background.mutate();
+ NumPadAnimator(Context context, LayerDrawable drawable, @StyleRes int style) {
+ LayerDrawable ld = (LayerDrawable) drawable.mutate();
+ mBackground = (GradientDrawable) ld.findDrawableByLayerId(R.id.background);
+ mRipple = (RippleDrawable) ld.findDrawableByLayerId(R.id.ripple);
+ mRippleMask = (GradientDrawable) mRipple.findDrawableByLayerId(android.R.id.mask);
mStyle = style;
reloadColors(context);
@@ -49,13 +56,14 @@
// Actual values will be updated later, usually during an onLayout() call
mAnimator = ValueAnimator.ofFloat(0f);
- mAnimator.setDuration(250);
- mAnimator.setInterpolator(Interpolators.LINEAR);
+ mAnimator.setDuration(100);
+ mAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ mAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ mAnimator.setRepeatCount(1);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator anim) {
mBackground.setCornerRadius((float) anim.getAnimatedValue());
- mBackground.setColor(ColorUtils.blendARGB(mHighlightColor, mNormalColor,
- anim.getAnimatedFraction()));
+ mRippleMask.setCornerRadius((float) anim.getAnimatedValue());
}
});
@@ -66,9 +74,9 @@
}
void onLayout(int height) {
- float startRadius = height / 10f;
- float endRadius = height / 2f;
- mBackground.setCornerRadius(endRadius);
+ float startRadius = height / 2f;
+ float endRadius = height / 4f;
+ mBackground.setCornerRadius(startRadius);
mAnimator.setFloatValues(startRadius, endRadius);
}
@@ -91,6 +99,7 @@
a.recycle();
mBackground.setColor(mNormalColor);
+ mRipple.setColor(ColorStateList.valueOf(mHighlightColor));
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 886c372..8cb1bc4 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -17,7 +17,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
-import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.VectorDrawable;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
@@ -37,7 +37,7 @@
public NumPadButton(Context context, AttributeSet attrs) {
super(context, attrs);
- mAnimator = new NumPadAnimator(context, (GradientDrawable) getBackground(),
+ mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
attrs.getStyleAttribute());
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 01e1c63..a4a781d 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -18,7 +18,7 @@
import android.content.Context;
import android.content.res.TypedArray;
-import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
import android.os.PowerManager;
import android.os.SystemClock;
import android.util.AttributeSet;
@@ -127,7 +127,7 @@
setContentDescription(mDigitText.getText().toString());
- mAnimator = new NumPadAnimator(context, (GradientDrawable) getBackground(),
+ mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
R.style.NumPadKey);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index b6a232d..71ec33e 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -16,10 +16,7 @@
package com.android.systemui;
-import android.app.WallpaperColors;
-import android.graphics.Bitmap;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
@@ -29,16 +26,13 @@
import android.util.Size;
import android.view.SurfaceHolder;
-import androidx.annotation.NonNull;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.glwallpaper.EglHelper;
+import com.android.systemui.glwallpaper.GLWallpaperRenderer;
import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
import javax.inject.Inject;
@@ -51,13 +45,8 @@
// We delayed destroy render context that subsequent render requests have chance to cancel it.
// This is to avoid destroying then recreating render context in a very short time.
private static final int DELAY_FINISH_RENDERING = 1000;
- private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS =
- new RectF(0, 0, 1, 1);
private static final boolean DEBUG = false;
- private ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
private HandlerThread mWorker;
- // scaled down version
- private Bitmap mMiniBitmap;
@Inject
public ImageWallpaper() {
@@ -81,7 +70,6 @@
super.onDestroy();
mWorker.quitSafely();
mWorker = null;
- mMiniBitmap = null;
}
class GLEngine extends Engine {
@@ -92,7 +80,7 @@
@VisibleForTesting
static final int MIN_SURFACE_HEIGHT = 64;
- private ImageWallpaperRenderer mRenderer;
+ private GLWallpaperRenderer mRenderer;
private EglHelper mEglHelper;
private final Runnable mFinishRenderingTask = this::finishRendering;
private boolean mNeedRedraw;
@@ -113,12 +101,6 @@
setFixedSizeAllowed(true);
setOffsetNotificationsEnabled(false);
updateSurfaceSize();
- mMiniBitmap = null;
- if (mWorker == null || mWorker.getThreadHandler() == null) {
- updateMiniBitmap();
- } else {
- mWorker.getThreadHandler().post(this::updateMiniBitmap);
- }
}
EglHelper getEglHelperInstance() {
@@ -129,20 +111,6 @@
return new ImageWallpaperRenderer(getDisplayContext());
}
- private void updateMiniBitmap() {
- mRenderer.useBitmap(b -> {
- int size = Math.min(b.getWidth(), b.getHeight());
- float scale = 1.0f;
- if (size > MIN_SURFACE_WIDTH) {
- scale = (float) MIN_SURFACE_WIDTH / (float) size;
- }
- mMiniBitmap = Bitmap.createScaledBitmap(b, Math.round(scale * b.getWidth()),
- Math.round(scale * b.getHeight()), false);
- computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap);
- mLocalColorsToAdd.clear();
- });
- }
-
private void updateSurfaceSize() {
SurfaceHolder holder = getSurfaceHolder();
Size frameSize = mRenderer.reportSurfaceSize();
@@ -158,7 +126,6 @@
@Override
public void onDestroy() {
- mMiniBitmap = null;
mWorker.getThreadHandler().post(() -> {
mRenderer.finish();
mRenderer = null;
@@ -167,61 +134,6 @@
});
}
-
-
- @Override
- public boolean supportsLocalColorExtraction() {
- return true;
- }
-
- @Override
- public void addLocalColorsAreas(@NonNull List<RectF> regions) {
- mWorker.getThreadHandler().post(() -> {
- Bitmap bitmap = mMiniBitmap;
- if (bitmap == null) {
- mLocalColorsToAdd.addAll(regions);
- } else {
- computeAndNotifyLocalColors(regions, bitmap);
- }
- });
- }
-
- private void computeAndNotifyLocalColors(@NonNull List<RectF> regions, Bitmap b) {
- List<WallpaperColors> colors = getLocalWallpaperColors(regions, b);
- try {
- notifyLocalColorsChanged(regions, colors);
- } catch (RuntimeException e) {
- Log.e(TAG, e.getMessage(), e);
- }
- }
-
- @Override
- public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
- // No-OP
- }
-
- private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
- Bitmap b) {
- List<WallpaperColors> colors = new ArrayList<>(areas.size());
- for (int i = 0; i < areas.size(); i++) {
- RectF area = areas.get(i);
- if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
- colors.add(null);
- continue;
- }
- Rect subImage = new Rect(
- Math.round(area.left * b.getWidth()),
- Math.round(area.top * b.getHeight()),
- Math.round(area.right * b.getWidth()),
- Math.round(area.bottom * b.getHeight()));
- Bitmap colorImg = Bitmap.createBitmap(b,
- subImage.left, subImage.top, subImage.width(), subImage.height());
- WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
- colors.add(color);
- }
- return colors;
- }
-
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
if (mWorker == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 78f7966..865ca40 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -19,18 +19,15 @@
import android.app.ActivityThread;
import android.app.Application;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Process;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Log;
import android.util.TimingsTraceLog;
import android.view.SurfaceControl;
@@ -40,7 +37,6 @@
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
import com.android.systemui.shared.system.ThreadedRendererCompat;
import com.android.systemui.util.NotificationChannels;
@@ -125,21 +121,6 @@
mServices[i].onBootCompleted();
}
}
-
- // If SHOW_PEOPLE_SPACE is true, enable People Space widget provider.
- // TODO(b/170396074): Migrate to new feature flag (go/silk-flags-howto)
- try {
- int showPeopleSpace = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.SHOW_PEOPLE_SPACE, 1);
- context.getPackageManager().setComponentEnabledSetting(
- new ComponentName(context, PeopleSpaceWidgetProvider.class),
- showPeopleSpace == 1
- ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
- : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP);
- } catch (Exception e) {
- Log.w(TAG, "Error enabling People Space widget:", e);
- }
}
}, bootCompletedFilter);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index e5c9d10..ec3188a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.media.systemsounds.HomeSoundEffectController;
+import com.android.systemui.people.widget.PeopleSpaceWidgetEnabler;
import com.android.systemui.power.PowerUI;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsModule;
@@ -177,4 +178,10 @@
@IntoMap
@ClassKey(HomeSoundEffectController.class)
public abstract SystemUI bindHomeSoundEffectController(HomeSoundEffectController sysui);
+
+ /** Inject into PeopleSpaceWidgetEnabler. */
+ @Binds
+ @IntoMap
+ @ClassKey(PeopleSpaceWidgetEnabler.class)
+ public abstract SystemUI bindPeopleSpaceWidgetEnabler(PeopleSpaceWidgetEnabler sysui);
}
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 01a353c..1a0356c 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -58,14 +58,6 @@
mWallpaper = new ImageGLWallpaper(mProgram);
}
- /**
- * @hide
- * @return
- */
- public void useBitmap(Consumer<Bitmap> c) {
- mTexture.use(c);
- }
-
@Override
public boolean isWcgContent() {
return mTexture.isWcgContent();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
index 3a06f7a..2873cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
import android.view.View;
/**
@@ -29,7 +30,7 @@
* See {@link com.android.systemui.statusbar.phone.KeyguardBottomAreaView}.
*/
public class KeyguardIndication {
- @NonNull
+ @Nullable
private final CharSequence mMessage;
@NonNull
private final ColorStateList mTextColor;
@@ -56,7 +57,7 @@
/**
* Message to display
*/
- public @NonNull CharSequence getMessage() {
+ public @Nullable CharSequence getMessage() {
return mMessage;
}
@@ -88,6 +89,17 @@
return mBackground;
}
+ @Override
+ public String toString() {
+ String str = "KeyguardIndication{";
+ if (!TextUtils.isEmpty(mMessage)) str += "mMessage=" + mMessage;
+ if (mIcon != null) str += " mIcon=" + mIcon;
+ if (mOnClickListener != null) str += " mOnClickListener=" + mOnClickListener;
+ if (mBackground != null) str += " mBackground=" + mBackground;
+ str += "}";
+ return str;
+ }
+
/**
* KeyguardIndication Builder
*/
@@ -101,7 +113,7 @@
public Builder() { }
/**
- * Required field. Message to display.
+ * Message to display. Indication requires a non-null message or icon.
*/
public Builder setMessage(@NonNull CharSequence message) {
this.mMessage = message;
@@ -117,9 +129,9 @@
}
/**
- * Optional. Icon to show next to the text. Icon location changes based on language
- * display direction. For LTR, icon shows to the left of the message. For RTL, icon shows
- * to the right of the message.
+ * Icon to show next to the text. Indication requires a non-null icon or message.
+ * Icon location changes based on language display direction. For LTR, icon shows to the
+ * left of the message. For RTL, icon shows to the right of the message.
*/
public Builder setIcon(Drawable icon) {
this.mIcon = icon;
@@ -146,8 +158,13 @@
* Build the KeyguardIndication.
*/
public KeyguardIndication build() {
- if (mMessage == null) throw new IllegalStateException("message must be set");
- if (mTextColor == null) throw new IllegalStateException("text color must be set");
+ if (TextUtils.isEmpty(mMessage) && mIcon == null) {
+ throw new IllegalStateException("message or icon must be set");
+ }
+ if (mTextColor == null) {
+ throw new IllegalStateException("text color must be set");
+ }
+
return new KeyguardIndication(
mMessage, mTextColor, mIcon, mOnClickListener, mBackground);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 8c04143..d4678f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -19,7 +19,6 @@
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.graphics.Color;
-import android.text.TextUtils;
import android.view.View;
import androidx.annotation.IntDef;
@@ -105,8 +104,7 @@
public void updateIndication(@IndicationType int type, KeyguardIndication newIndication,
boolean showImmediately) {
final boolean hasPreviousIndication = mIndicationMessages.get(type) != null;
- final boolean hasNewIndication = newIndication != null
- && !TextUtils.isEmpty(newIndication.getMessage());
+ final boolean hasNewIndication = newIndication != null;
if (!hasNewIndication) {
mIndicationMessages.remove(type);
mIndicationQueue.removeIf(x -> x == type);
@@ -203,7 +201,6 @@
mIndicationQueue.add(type); // re-add to show later
}
- // pass the style update to be run right before our new indication is shown:
mView.switchIndication(mIndicationMessages.get(type));
// only schedule next indication if there's more than just this indication in the queue
@@ -289,8 +286,7 @@
if (hasIndications()) {
pw.println(" All messages:");
for (int type : mIndicationMessages.keySet()) {
- pw.println(" type=" + type
- + " message=" + mIndicationMessages.get(type).getMessage());
+ pw.println(" type=" + type + " " + mIndicationMessages.get(type));
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index c67aef6..2f9b17a 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -19,6 +19,8 @@
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.getUserHandle;
+
import android.app.Activity;
import android.app.INotificationManager;
import android.app.people.IPeopleManager;
@@ -39,6 +41,7 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
@@ -49,6 +52,7 @@
public class PeopleSpaceActivity extends Activity {
private static final String TAG = "PeopleSpaceActivity";
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
private ViewGroup mPeopleSpaceLayout;
private IPeopleManager mPeopleManager;
@@ -134,9 +138,11 @@
/** Stores the user selected configuration for {@code mAppWidgetId}. */
private void storeWidgetConfiguration(PeopleSpaceTile tile) {
if (PeopleSpaceUtils.DEBUG) {
- Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
- + tile.getId() + " for widget ID: "
- + mAppWidgetId);
+ if (DEBUG) {
+ Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
+ + tile.getId() + " for widget ID: "
+ + mAppWidgetId);
+ }
}
PeopleSpaceUtils.setStorageForTile(mContext, tile, mAppWidgetId);
@@ -144,12 +150,22 @@
// TODO: Populate new widget with existing conversation notification, if there is any.
PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager,
mPeopleManager);
+ if (mLauncherApps != null) {
+ try {
+ if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId());
+ mLauncherApps.cacheShortcuts(tile.getPackageName(),
+ Collections.singletonList(tile.getId()),
+ getUserHandle(tile), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
+ } catch (Exception e) {
+ Log.w(TAG, "Exception caching shortcut:" + e);
+ }
+ }
finishActivity();
}
/** Finish activity with a successful widget configuration result. */
private void finishActivity() {
- if (PeopleSpaceUtils.DEBUG) Log.d(TAG, "Widget added!");
+ if (DEBUG) Log.d(TAG, "Widget added!");
mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_ADDED);
setActivityResult(RESULT_OK);
finish();
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 7eb1fc1..cd1131b 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -17,12 +17,21 @@
package com.android.systemui.people;
import static android.app.Notification.EXTRA_MESSAGES;
+import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
+import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.ACTIVITY_LOCATION;
+import static android.app.people.ConversationStatus.ACTIVITY_MEDIA;
+import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
+import static android.app.people.ConversationStatus.ACTIVITY_UPCOMING_BIRTHDAY;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.people.ConversationChannel;
+import android.app.people.ConversationStatus;
import android.app.people.IPeopleManager;
import android.app.people.PeopleSpaceTile;
import android.appwidget.AppWidgetManager;
@@ -37,6 +46,7 @@
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.icu.text.MeasureFormat;
import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
@@ -71,6 +81,7 @@
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
@@ -287,7 +298,7 @@
SharedPreferences.Editor widgetEditor = widgetSp.edit();
widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, tile.getPackageName());
widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, tile.getId());
- int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+ int userId = getUserId(tile);
widgetEditor.putInt(PeopleSpaceUtils.USER_ID, userId);
widgetEditor.apply();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
@@ -441,20 +452,141 @@
/** Creates a {@link RemoteViews} for {@code tile}. */
public static RemoteViews createRemoteViews(Context context,
PeopleSpaceTile tile, int appWidgetId) {
- RemoteViews views;
+ RemoteViews viewsForTile = getViewForTile(context, tile);
+ RemoteViews views = setCommonRemoteViewsFields(context, viewsForTile, tile);
+ return setLaunchIntents(context, views, tile, appWidgetId);
+ }
+
+ /**
+ * The prioritization for the {@code tile} content is missed calls, followed by notification
+ * content, then birthdays, then the most recent status, and finally last interaction.
+ */
+ private static RemoteViews getViewForTile(Context context, PeopleSpaceTile tile) {
if (tile.getNotificationKey() != null) {
- views = createNotificationRemoteViews(context, tile);
- } else if (tile.getBirthdayText() != null) {
- views = createStatusRemoteViews(context, tile);
- } else {
- views = createLastInteractionRemoteViews(context, tile);
+ if (DEBUG) Log.d(TAG, "Create notification view");
+ return createNotificationRemoteViews(context, tile);
}
- return setCommonRemoteViewsFields(context, views, tile, appWidgetId);
+
+ // TODO: Add sorting when we expose timestamp of statuses.
+ List<ConversationStatus> statusesForEntireView =
+ tile.getStatuses() == null ? Arrays.asList() : tile.getStatuses().stream().filter(
+ c -> isStatusValidForEntireStatusView(c)).collect(Collectors.toList());
+ ConversationStatus birthdayStatus = getBirthdayStatus(tile, statusesForEntireView);
+ if (birthdayStatus != null) {
+ if (DEBUG) Log.d(TAG, "Create birthday view");
+ return createStatusRemoteViews(context, birthdayStatus);
+ }
+
+ if (!statusesForEntireView.isEmpty()) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "Create status view for: " + statusesForEntireView.get(0).getActivity());
+ }
+ return createStatusRemoteViews(context, statusesForEntireView.get(0));
+ }
+
+ return createLastInteractionRemoteViews(context, tile);
+ }
+
+ @Nullable
+ private static ConversationStatus getBirthdayStatus(PeopleSpaceTile tile,
+ List<ConversationStatus> statuses) {
+ Optional<ConversationStatus> birthdayStatus = statuses.stream().filter(
+ c -> c.getActivity() == ACTIVITY_BIRTHDAY).findFirst();
+ if (birthdayStatus.isPresent()) {
+ return birthdayStatus.get();
+ }
+ if (!TextUtils.isEmpty(tile.getBirthdayText())) {
+ return new ConversationStatus.Builder(tile.getId(), ACTIVITY_BIRTHDAY).build();
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns whether a {@code status} should have its own entire templated view.
+ *
+ * <p>A status may still be shown on the view (for example, as a new story ring) even if it's
+ * not valid to compose an entire view.
+ */
+ private static boolean isStatusValidForEntireStatusView(ConversationStatus status) {
+ switch (status.getActivity()) {
+ // Birthday & Anniversary don't require text provided or icon provided.
+ case ACTIVITY_BIRTHDAY:
+ case ACTIVITY_ANNIVERSARY:
+ return true;
+ default:
+ // For future birthday, location, new story, video, music, game, and other, the
+ // app must provide either text or an icon.
+ return !TextUtils.isEmpty(status.getDescription())
+ || status.getIcon() != null;
+ }
+ }
+
+ private static RemoteViews createStatusRemoteViews(Context context, ConversationStatus status) {
+ RemoteViews views = new RemoteViews(
+ context.getPackageName(), R.layout.people_space_small_avatar_tile);
+ CharSequence statusText = status.getDescription();
+ if (TextUtils.isEmpty(statusText)) {
+ statusText = getStatusTextByType(context, status.getActivity());
+ }
+ views.setTextViewText(R.id.status, statusText);
+ Icon statusIcon = status.getIcon();
+ if (statusIcon != null) {
+ views.setImageViewIcon(R.id.image, statusIcon);
+ views.setBoolean(R.id.content_background, "setClipToOutline", true);
+ } else {
+ views.setViewVisibility(R.id.content_background, View.GONE);
+ }
+ // TODO: Set status pre-defined icons
+ return views;
+ }
+
+ private static String getStatusTextByType(Context context, int activity) {
+ switch (activity) {
+ case ACTIVITY_BIRTHDAY:
+ return context.getString(R.string.birthday_status);
+ case ACTIVITY_UPCOMING_BIRTHDAY:
+ return context.getString(R.string.upcoming_birthday_status);
+ case ACTIVITY_ANNIVERSARY:
+ return context.getString(R.string.anniversary_status);
+ case ACTIVITY_LOCATION:
+ return context.getString(R.string.location_status);
+ case ACTIVITY_NEW_STORY:
+ return context.getString(R.string.new_story_status);
+ case ACTIVITY_MEDIA:
+ return context.getString(R.string.video_status);
+ case ACTIVITY_GAME:
+ return context.getString(R.string.game_status);
+ default:
+ return EMPTY_STRING;
+ }
}
private static RemoteViews setCommonRemoteViewsFields(Context context, RemoteViews views,
- PeopleSpaceTile tile, int appWidgetId) {
+ PeopleSpaceTile tile) {
try {
+ boolean isAvailable =
+ tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
+ c -> c.getAvailability() == AVAILABILITY_AVAILABLE);
+ if (isAvailable) {
+ views.setViewVisibility(R.id.availability, View.VISIBLE);
+ } else {
+ views.setViewVisibility(R.id.availability, View.GONE);
+ }
+ boolean hasNewStory =
+ tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
+ c -> c.getActivity() == ACTIVITY_NEW_STORY);
+ if (hasNewStory) {
+ views.setViewVisibility(R.id.person_icon_with_story, View.VISIBLE);
+ views.setViewVisibility(R.id.person_icon_only, View.GONE);
+ views.setImageViewIcon(R.id.person_icon_inside_ring, tile.getUserIcon());
+ } else {
+ views.setViewVisibility(R.id.person_icon_with_story, View.GONE);
+ views.setViewVisibility(R.id.person_icon_only, View.VISIBLE);
+ views.setImageViewIcon(R.id.person_icon_only, tile.getUserIcon());
+ }
+
views.setTextViewText(R.id.name, tile.getUserName().toString());
views.setImageViewIcon(R.id.person_icon, tile.getUserIcon());
views.setBoolean(R.id.content_background, "setClipToOutline", true);
@@ -466,7 +598,16 @@
tile.getPackageName())
)
);
+ return views;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to set common fields: " + e);
+ }
+ return views;
+ }
+ private static RemoteViews setLaunchIntents(Context context, RemoteViews views,
+ PeopleSpaceTile tile, int appWidgetId) {
+ try {
Intent activityIntent = new Intent(context, LaunchConversationActivity.class);
activityIntent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
@@ -484,48 +625,42 @@
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE));
return views;
} catch (Exception e) {
- Log.e(TAG, "Failed to set common fields: " + e);
+ Log.e(TAG, "Failed to add launch intents: " + e);
}
- return null;
+ return views;
}
private static RemoteViews createNotificationRemoteViews(Context context,
PeopleSpaceTile tile) {
RemoteViews views = new RemoteViews(
- context.getPackageName(), R.layout.people_space_small_avatar_tile);
+ context.getPackageName(), R.layout.people_space_notification_content_tile);
Uri image = tile.getNotificationDataUri();
if (image != null) {
- //TODO: Use NotificationInlineImageCache
+ // TODO: Use NotificationInlineImageCache
views.setImageViewUri(R.id.image, image);
- views.setViewVisibility(R.id.image, View.VISIBLE);
+ views.setViewVisibility(R.id.content_background, View.VISIBLE);
+ views.setBoolean(R.id.content_background, "setClipToOutline", true);
views.setViewVisibility(R.id.content, View.GONE);
} else {
CharSequence content = tile.getNotificationContent();
views = setPunctuationRemoteViewsFields(views, content);
views.setTextViewText(R.id.content, content);
views.setViewVisibility(R.id.content, View.VISIBLE);
- views.setViewVisibility(R.id.image, View.GONE);
+ views.setViewVisibility(R.id.content_background, View.GONE);
}
- views.setTextViewText(R.id.time, PeopleSpaceUtils.getLastInteractionString(
+ // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile.
+ views.setTextViewText(R.id.subtext, PeopleSpaceUtils.getLastInteractionString(
context, tile.getLastInteractionTimestamp(), false));
return views;
}
- private static RemoteViews createStatusRemoteViews(Context context,
- PeopleSpaceTile tile) {
- RemoteViews views = new RemoteViews(
- context.getPackageName(), R.layout.people_space_large_avatar_tile);
- views.setTextViewText(R.id.status, tile.getBirthdayText());
- return views;
- }
-
private static RemoteViews createLastInteractionRemoteViews(Context context,
PeopleSpaceTile tile) {
RemoteViews views = new RemoteViews(
context.getPackageName(), R.layout.people_space_large_avatar_tile);
String status = PeopleSpaceUtils.getLastInteractionString(
context, tile.getLastInteractionTimestamp(), true);
- views.setTextViewText(R.id.status, status);
+ views.setTextViewText(R.id.last_interaction, status);
return views;
}
@@ -634,7 +769,7 @@
private static Long getLastInteraction(IPeopleManager peopleManager,
PeopleSpaceTile tile) {
try {
- int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+ int userId = getUserId(tile);
String pkg = tile.getPackageName();
return peopleManager.getLastInteraction(pkg, userId, tile.getId());
} catch (Exception e) {
@@ -682,20 +817,35 @@
Duration durationSinceLastInteraction = Duration.ofMillis(now - lastInteraction);
MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(),
MeasureFormat.FormatWidth.WIDE);
+ MeasureFormat shortFormatter = MeasureFormat.getInstance(Locale.getDefault(),
+ MeasureFormat.FormatWidth.SHORT);
if (durationSinceLastInteraction.toHours() < MIN_HOUR) {
- return context.getString(includeLastChatted ? R.string.last_interaction_status_less_than
- : R.string.less_than_timestamp,
- formatter.formatMeasures(new Measure(MIN_HOUR, MeasureUnit.HOUR)));
+ if (includeLastChatted) {
+ return context.getString(R.string.last_interaction_status_less_than,
+ formatter.formatMeasures(new Measure(MIN_HOUR, MeasureUnit.HOUR)));
+ }
+ return context.getString(R.string.timestamp, shortFormatter.formatMeasures(
+ new Measure(durationSinceLastInteraction.toMinutes(), MeasureUnit.MINUTE)));
} else if (durationSinceLastInteraction.toDays() < ONE_DAY) {
- return context.getString(
- includeLastChatted ? R.string.last_interaction_status : R.string.timestamp,
- formatter.formatMeasures(
- new Measure(durationSinceLastInteraction.toHours(), MeasureUnit.HOUR)));
+ if (includeLastChatted) {
+ return context.getString(R.string.last_interaction_status,
+ formatter.formatMeasures(
+ new Measure(durationSinceLastInteraction.toHours(),
+ MeasureUnit.HOUR)));
+ }
+ return context.getString(R.string.timestamp, shortFormatter.formatMeasures(
+ new Measure(durationSinceLastInteraction.toHours(),
+ MeasureUnit.HOUR)));
} else if (durationSinceLastInteraction.toDays() < DAYS_IN_A_WEEK) {
- return context.getString(
- includeLastChatted ? R.string.last_interaction_status : R.string.timestamp,
- formatter.formatMeasures(
- new Measure(durationSinceLastInteraction.toDays(), MeasureUnit.DAY)));
+ if (includeLastChatted) {
+ return context.getString(R.string.last_interaction_status,
+ formatter.formatMeasures(
+ new Measure(durationSinceLastInteraction.toDays(),
+ MeasureUnit.DAY)));
+ }
+ return context.getString(R.string.timestamp, shortFormatter.formatMeasures(
+ new Measure(durationSinceLastInteraction.toHours(),
+ MeasureUnit.DAY)));
} else {
return context.getString(durationSinceLastInteraction.toDays() == DAYS_IN_A_WEEK
? (includeLastChatted ? R.string.last_interaction_status :
@@ -883,4 +1033,14 @@
public static String getKey(String shortcutId, String packageName, int userId) {
return shortcutId + "/" + userId + "/" + packageName;
}
+
+ /** Returns the userId associated with a {@link PeopleSpaceTile} */
+ public static int getUserId(PeopleSpaceTile tile) {
+ return getUserHandle(tile).getIdentifier();
+ }
+
+ /** Returns the {@link UserHandle} associated with a {@link PeopleSpaceTile} */
+ public static UserHandle getUserHandle(PeopleSpaceTile tile) {
+ return UserHandle.getUserHandleForUid(tile.getUid());
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java
new file mode 100644
index 0000000..b188acb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetEnabler.java
@@ -0,0 +1,61 @@
+/*
+ * 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.systemui.people.widget;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.FeatureFlags;
+
+import javax.inject.Inject;
+
+/**
+ * Enables People Space widgets.
+ */
+@SysUISingleton
+public class PeopleSpaceWidgetEnabler extends SystemUI {
+ private static final String TAG = "PeopleSpaceWdgtEnabler";
+ private Context mContext;
+ private FeatureFlags mFeatureFlags;
+
+ @Inject
+ public PeopleSpaceWidgetEnabler(Context context, FeatureFlags featureFlags) {
+ super(context);
+ mContext = context;
+ mFeatureFlags = featureFlags;
+ }
+
+ @Override
+ public void start() {
+ Log.d(TAG, "Starting service");
+ try {
+ boolean showPeopleSpace = mFeatureFlags.isPeopleTileEnabled();
+ mContext.getPackageManager().setComponentEnabledSetting(
+ new ComponentName(mContext, PeopleSpaceWidgetProvider.class),
+ showPeopleSpace
+ ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ } catch (Exception e) {
+ Log.w(TAG, "Error enabling People Space widget:", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
index f5577d3..3d1055f 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -16,13 +16,20 @@
package com.android.systemui.people.widget;
+import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
+import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
+
import android.app.PendingIntent;
import android.app.people.IPeopleManager;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.LauncherApps;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import android.widget.RemoteViews;
@@ -32,6 +39,8 @@
import com.android.systemui.R;
import com.android.systemui.people.PeopleSpaceUtils;
+import java.util.Collections;
+
/** People Space Widget Provider class. */
public class PeopleSpaceWidgetProvider extends AppWidgetProvider {
private static final String TAG = "PeopleSpaceWidgetPvd";
@@ -88,11 +97,31 @@
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
+ LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+
for (int widgetId : appWidgetIds) {
if (DEBUG) Log.d(TAG, "Widget removed");
mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED);
+ if (launcherApps != null) {
+ SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId),
+ Context.MODE_PRIVATE);
+ String packageName = widgetSp.getString(PACKAGE_NAME, null);
+ String shortcutId = widgetSp.getString(SHORTCUT_ID, null);
+ int userId = widgetSp.getInt(USER_ID, -1);
+
+ if (packageName != null && shortcutId != null && userId != -1) {
+ try {
+ if (DEBUG) Log.d(TAG, "Uncaching shortcut for PeopleTile: " + shortcutId);
+ launcherApps.uncacheShortcuts(packageName,
+ Collections.singletonList(shortcutId),
+ UserHandle.of(userId),
+ LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
+ } catch (Exception e) {
+ Log.d(TAG, "Exception uncaching shortcut:" + e);
+ }
+ }
+ }
PeopleSpaceUtils.removeStorageForTile(context, widgetId);
}
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 3866382..1411fa1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -170,7 +170,8 @@
// Update visibility of footer
mIsVisible = (isDeviceManaged && !isDemoDevice) || hasCACerts || hasCACertsInWorkProfile
|| vpnName != null || vpnNameWorkProfile != null
- || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled;
+ || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled
+ || (hasWorkProfile && isNetworkLoggingEnabled);
// Update the string
mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile,
hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
@@ -275,12 +276,30 @@
vpnName);
}
if (isProfileOwnerOfOrganizationOwnedDevice) {
+ if (isNetworkLoggingEnabled) {
+ if (organizationName == null) {
+ return mContext.getString(
+ R.string.quick_settings_disclosure_management_monitoring);
+ }
+ return mContext.getString(
+ R.string.quick_settings_disclosure_named_management_monitoring,
+ organizationName);
+ }
if (workProfileOrganizationName == null) {
return mContext.getString(R.string.quick_settings_disclosure_management);
}
return mContext.getString(R.string.quick_settings_disclosure_named_management,
workProfileOrganizationName);
}
+ if (hasWorkProfile && isNetworkLoggingEnabled) {
+ if (workProfileOrganizationName == null) {
+ return mContext.getString(
+ R.string.quick_settings_disclosure_managed_profile_monitoring);
+ }
+ return mContext.getString(
+ R.string.quick_settings_disclosure_named_managed_profile_monitoring,
+ workProfileOrganizationName);
+ }
return null;
}
@@ -367,7 +386,8 @@
}
// network logging section
- CharSequence networkLoggingMessage = getNetworkLoggingMessage(isNetworkLoggingEnabled);
+ CharSequence networkLoggingMessage = getNetworkLoggingMessage(isDeviceManaged,
+ isNetworkLoggingEnabled);
if (networkLoggingMessage == null) {
dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.GONE);
} else {
@@ -492,9 +512,15 @@
return mContext.getString(R.string.monitoring_description_ca_certificate);
}
- protected CharSequence getNetworkLoggingMessage(boolean isNetworkLoggingEnabled) {
+ protected CharSequence getNetworkLoggingMessage(boolean isDeviceManaged,
+ boolean isNetworkLoggingEnabled) {
if (!isNetworkLoggingEnabled) return null;
- return mContext.getString(R.string.monitoring_description_management_network_logging);
+ if (isDeviceManaged) {
+ return mContext.getString(R.string.monitoring_description_management_network_logging);
+ } else {
+ return mContext.getString(
+ R.string.monitoring_description_managed_profile_network_logging);
+ }
}
protected CharSequence getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index 9383aef..1386ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -24,15 +24,21 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Bundle;
import android.util.AttributeSet;
+import android.util.IntArray;
import android.util.Log;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.Nullable;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
+import com.android.internal.widget.ExploreByTouchHelper;
import com.android.systemui.R;
/**
@@ -80,6 +86,8 @@
t.recycle();
// 48 dp touchable region around each handle.
mCropTouchMargin = 24 * getResources().getDisplayMetrics().density;
+
+ setAccessibilityDelegate(new AccessibilityHelper());
}
@Override
@@ -231,6 +239,87 @@
return CropBoundary.NONE;
}
+ private class AccessibilityHelper extends ExploreByTouchHelper {
+
+ private static final int TOP_HANDLE_ID = 1;
+ private static final int BOTTOM_HANDLE_ID = 2;
+
+ AccessibilityHelper() {
+ super(CropView.this);
+ }
+
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ if (Math.abs(y - fractionToPixels(mTopCrop)) < mCropTouchMargin) {
+ return TOP_HANDLE_ID;
+ }
+ if (Math.abs(y - fractionToPixels(mBottomCrop)) < mCropTouchMargin) {
+ return BOTTOM_HANDLE_ID;
+ }
+ return ExploreByTouchHelper.INVALID_ID;
+ }
+
+ @Override
+ protected void getVisibleVirtualViews(IntArray virtualViewIds) {
+ virtualViewIds.add(TOP_HANDLE_ID);
+ virtualViewIds.add(BOTTOM_HANDLE_ID);
+ }
+
+ @Override
+ protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+ switch(virtualViewId) {
+ case TOP_HANDLE_ID:
+ event.setContentDescription(
+ getResources().getString(R.string.screenshot_top_boundary));
+ break;
+ case BOTTOM_HANDLE_ID:
+ event.setContentDescription(
+ getResources().getString(R.string.screenshot_bottom_boundary));
+ break;
+ }
+ }
+
+ @Override
+ protected void onPopulateNodeForVirtualView(int virtualViewId,
+ AccessibilityNodeInfo node) {
+ switch(virtualViewId) {
+ case TOP_HANDLE_ID:
+ node.setContentDescription(
+ getResources().getString(R.string.screenshot_top_boundary));
+ setNodePositions(mTopCrop, node);
+ break;
+ case BOTTOM_HANDLE_ID:
+ node.setContentDescription(
+ getResources().getString(R.string.screenshot_bottom_boundary));
+ setNodePositions(mBottomCrop, node);
+ break;
+ }
+
+ // TODO: need to figure out the full set of actions to support here.
+ node.addAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
+ node.setClickable(true);
+ node.setFocusable(true);
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(
+ int virtualViewId, int action, Bundle arguments) {
+ return false;
+ }
+
+ private void setNodePositions(float fraction, AccessibilityNodeInfo node) {
+ int pixels = fractionToPixels(fraction);
+ Rect rect = new Rect(0, (int) (pixels - mCropTouchMargin),
+ getWidth(), (int) (pixels + mCropTouchMargin));
+ node.setBoundsInParent(rect);
+ int[] pos = new int[2];
+ getLocationOnScreen(pos);
+ rect.offset(pos[0], pos[1]);
+ node.setBoundsInScreen(rect);
+ }
+ }
+
/**
* Listen for crop motion events and state.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index d707bca0..9f182e1 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -28,6 +28,7 @@
import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA
import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE
import android.os.Bundle
+import android.os.Handler
import android.text.Html
import android.util.Log
import com.android.internal.app.AlertActivity
@@ -43,10 +44,13 @@
companion object {
private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName
+
+ private const val SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000L
}
private var sensor = -1
private lateinit var sensorUsePackageName: String
+ private var unsuppressImmediately = false
private lateinit var sensorPrivacyManager: SensorPrivacyManager
private lateinit var appOpsManager: AppOpsManager
@@ -118,6 +122,7 @@
super.onStart()
sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, true)
+ unsuppressImmediately = false
}
override fun onClick(dialog: DialogInterface?, which: Int) {
@@ -131,16 +136,16 @@
}
override fun onDismissSucceeded() {
- sensorPrivacyManager
- .setIndividualSensorPrivacyForProfileGroup(sensor, false)
- setResult(RESULT_OK)
+ disableSensorPrivacy()
}
})
} else {
- sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false)
- setResult(RESULT_OK)
+ disableSensorPrivacy()
}
}
+ BUTTON_NEGATIVE -> {
+ unsuppressImmediately = false
+ }
}
dismiss()
@@ -149,10 +154,24 @@
override fun onStop() {
super.onDestroy()
- sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false)
+ if (unsuppressImmediately) {
+ sensorPrivacyManager
+ .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false)
+ } else {
+ Handler(mainLooper).postDelayed({
+ sensorPrivacyManager
+ .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false)
+ }, SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS)
+ }
}
override fun onBackPressed() {
// do not allow backing out
}
+
+ private fun disableSensorPrivacy() {
+ sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false)
+ unsuppressImmediately = true
+ setResult(RESULT_OK)
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index c70a93b..7e2d27a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -208,7 +208,6 @@
mLockScreenMode);
updateIndication(false /* animate */);
updateDisclosure();
- updateOwnerInfo();
if (mBroadcastReceiver == null) {
// Update the disclosure proactively to avoid IPC on the critical path.
mBroadcastReceiver = new BroadcastReceiver() {
@@ -261,18 +260,21 @@
}
/**
- * Doesn't include owner information or disclosure which get triggered separately.
+ * Doesn't include disclosure which gets triggered separately.
*/
private void updateIndications(boolean animate, int userId) {
+ updateOwnerInfo();
updateBattery(animate);
updateUserLocked(userId);
updateTransient();
updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication());
updateAlignment();
+ updateLogoutView();
updateResting();
}
private void updateDisclosure() {
+ // avoid calling this method since it has an IPC
if (whitelistIpcs(this::isOrganizationOwnedDevice)) {
final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName();
final CharSequence disclosure = organizationName != null
@@ -291,7 +293,34 @@
}
if (isKeyguardLayoutEnabled()) {
- updateIndication(false); // resting indication may need to update
+ updateResting();
+ }
+ }
+
+ private void updateOwnerInfo() {
+ if (!isKeyguardLayoutEnabled()) {
+ mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO);
+ return;
+ }
+ String info = mLockPatternUtils.getDeviceOwnerInfo();
+ if (info == null) {
+ // Use the current user owner information if enabled.
+ final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (ownerInfoEnabled) {
+ info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+ }
+ }
+ if (info != null) {
+ mRotateTextViewController.updateIndication(
+ INDICATION_TYPE_OWNER_INFO,
+ new KeyguardIndication.Builder()
+ .setMessage(info)
+ .setTextColor(mInitialTextColorState)
+ .build(),
+ false);
+ } else {
+ mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO);
}
}
@@ -400,56 +429,34 @@
private void updateLogoutView() {
if (!isKeyguardLayoutEnabled()) {
+ mRotateTextViewController.hideIndication(INDICATION_TYPE_LOGOUT);
return;
}
final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled()
&& KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
- String logoutString = shouldShowLogout ? mContext.getResources().getString(
- com.android.internal.R.string.global_action_logout) : null;
- mRotateTextViewController.updateIndication(
- INDICATION_TYPE_LOGOUT,
- new KeyguardIndication.Builder()
- .setMessage(logoutString)
- .setTextColor(mInitialTextColorState)
- .setBackground(mContext.getDrawable(
- com.android.systemui.R.drawable.logout_button_background))
- .setClickListener((view) -> {
- int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
- try {
- mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
- mIActivityManager.stopUser(currentUserId, true /* force */, null);
- } catch (RemoteException re) {
- Log.e(TAG, "Failed to logout user", re);
- }
- })
- .build(),
- false);
- updateIndication(false); // resting indication may need to update
- }
-
- private void updateOwnerInfo() {
- if (!isKeyguardLayoutEnabled()) {
- return;
- }
- String info = mLockPatternUtils.getDeviceOwnerInfo();
- if (info == null) {
- // Use the current user owner information if enabled.
- final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
- KeyguardUpdateMonitor.getCurrentUser());
- if (ownerInfoEnabled) {
- info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
- }
- }
- if (info != null) {
+ if (shouldShowLogout) {
mRotateTextViewController.updateIndication(
- INDICATION_TYPE_OWNER_INFO,
+ INDICATION_TYPE_LOGOUT,
new KeyguardIndication.Builder()
- .setMessage(info)
+ .setMessage(mContext.getResources().getString(
+ com.android.internal.R.string.global_action_logout))
.setTextColor(mInitialTextColorState)
+ .setBackground(mContext.getDrawable(
+ com.android.systemui.R.drawable.logout_button_background))
+ .setClickListener((view) -> {
+ int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
+ try {
+ mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
+ mIActivityManager.stopUser(currentUserId, true /* force */,
+ null);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Failed to logout user", re);
+ }
+ })
.build(),
false);
} else {
- updateIndication(false); // resting indication may need to update
+ mRotateTextViewController.hideIndication(INDICATION_TYPE_LOGOUT);
}
}
@@ -1042,7 +1049,6 @@
@Override
public void onUserSwitchComplete(int userId) {
if (mVisible) {
- updateOwnerInfo();
updateIndication(false);
}
}
@@ -1057,7 +1063,7 @@
@Override
public void onLogoutEnabledChanged() {
if (mVisible) {
- updateLogoutView();
+ updateIndication(false);
}
}
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 8aadef8..2f9fa9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -68,7 +68,6 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
-import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintSet;
@@ -405,6 +404,7 @@
// Used for two finger gesture as well as accessibility shortcut to QS.
private boolean mQsExpandImmediate;
private boolean mTwoFingerQsExpandPossible;
+ private String mHeaderDebugInfo;
/**
* If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
@@ -3423,8 +3423,8 @@
return mView.getHeight();
}
- public TextView getHeaderDebugInfo() {
- return mView.findViewById(R.id.header_debug_info);
+ public void setHeaderDebugInfo(String text) {
+ if (DEBUG) mHeaderDebugInfo = text;
}
public void onThemeChanged() {
@@ -4087,6 +4087,8 @@
p.setStrokeWidth(2);
p.setStyle(Paint.Style.STROKE);
canvas.drawLine(0, getMaxPanelHeight(), mView.getWidth(), getMaxPanelHeight(), p);
+ p.setTextSize(24);
+ if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, p);
p.setColor(Color.BLUE);
canvas.drawLine(0, getExpandedHeight(), mView.getWidth(), getExpandedHeight(), p);
p.setColor(Color.GREEN);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 024a0b1..525f220 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -29,7 +29,6 @@
import android.service.vr.IVrStateCallbacks;
import android.util.Log;
import android.util.Slog;
-import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
@@ -162,11 +161,6 @@
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
- if (MULTIUSER_DEBUG) {
- mNotificationPanelDebugText = mNotificationPanel.getHeaderDebugInfo();
- mNotificationPanelDebugText.setVisibility(View.VISIBLE);
- }
-
IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
Context.VR_SERVICE));
if (vrManager != null) {
@@ -332,7 +326,7 @@
// Begin old BaseStatusBar.userSwitched
mHeadsUpManager.setUser(newUserId);
// End old BaseStatusBar.userSwitched
- if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
+ if (MULTIUSER_DEBUG) mNotificationPanel.setHeaderDebugInfo("USER " + newUserId);
mCommandQueue.animateCollapsePanels();
if (mReinflateNotificationsOnUserSwitched) {
updateNotificationsOnDensityOrFontScaleChanged();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 10332bd..0c69ffd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -100,7 +100,6 @@
return new ImageWallpaper() {
@Override
public Engine onCreateEngine() {
- onCreate();
return new GLEngine(mHandler) {
@Override
public Context getDisplayContext() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
new file mode 100644
index 0000000..b44fb8e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.systemui.keyguard;
+
+import static android.graphics.Color.WHITE;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class KeyguardIndicationTest extends SysuiTestCase {
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotCreateIndicationWithoutMessageOrIcon() {
+ new KeyguardIndication.Builder()
+ .setTextColor(ColorStateList.valueOf(WHITE))
+ .build();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotCreateIndicationWithoutColor() {
+ new KeyguardIndication.Builder()
+ .setMessage("message")
+ .build();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testCannotCreateIndicationWithEmptyMessage() {
+ new KeyguardIndication.Builder()
+ .setMessage("")
+ .setTextColor(ColorStateList.valueOf(WHITE))
+ .build();
+ }
+
+ @Test
+ public void testCreateIndicationWithMessage() {
+ final String text = "regular indication";
+ final KeyguardIndication indication = new KeyguardIndication.Builder()
+ .setMessage(text)
+ .setTextColor(ColorStateList.valueOf(WHITE))
+ .build();
+ assertEquals(text, indication.getMessage());
+ }
+
+ @Test
+ public void testCreateIndicationWithIcon() {
+ final KeyguardIndication indication = new KeyguardIndication.Builder()
+ .setIcon(mDrawable)
+ .setTextColor(ColorStateList.valueOf(WHITE))
+ .build();
+ assertEquals(mDrawable, indication.getIcon());
+ }
+
+ @Test
+ public void testCreateIndicationWithMessageAndIcon() {
+ final String text = "indication with msg and icon";
+ final KeyguardIndication indication = new KeyguardIndication.Builder()
+ .setMessage(text)
+ .setIcon(mDrawable)
+ .setTextColor(ColorStateList.valueOf(WHITE))
+ .build();
+ assertEquals(text, indication.getMessage());
+ assertEquals(mDrawable, indication.getIcon());
+ }
+
+ final Drawable mDrawable = new Drawable() {
+ @Override
+ public void draw(@NonNull Canvas canvas) { }
+
+ @Override
+ public void setAlpha(int alpha) { }
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) { }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+ };
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index 4ee2759..d79155c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -16,11 +16,18 @@
package com.android.systemui.people;
+import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+
import static com.android.systemui.people.PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE;
import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -39,6 +46,7 @@
import android.app.NotificationManager;
import android.app.Person;
import android.app.people.ConversationChannel;
+import android.app.people.ConversationStatus;
import android.app.people.IPeopleManager;
import android.app.people.PeopleSpaceTile;
import android.appwidget.AppWidgetManager;
@@ -46,13 +54,13 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
import android.database.Cursor;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.ContactsContract;
import android.provider.Settings;
@@ -60,6 +68,9 @@
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.TextView;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.systemui.R;
@@ -101,27 +112,45 @@
private static final int TEST_COLUMN_INDEX = 1;
private static final Uri URI = Uri.parse("fake_uri");
private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
+ private static final String GAME_DESCRIPTION = "Playing a game!";
+ private static final String NAME = "username";
private static final Person PERSON = new Person.Builder()
.setName("name")
.setKey("abc")
.setUri(URI.toString())
.setBot(false)
.build();
+ private static final PeopleSpaceTile PERSON_TILE_WITHOUT_NOTIFICATION =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+ .setLastInteractionTimestamp(0L)
+ .build();
private static final PeopleSpaceTile PERSON_TILE =
new PeopleSpaceTile
- .Builder(SHORTCUT_ID_1, "username", ICON, new Intent())
+ .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+ .setLastInteractionTimestamp(123L)
.setNotificationKey(NOTIFICATION_KEY)
.setNotificationContent(NOTIFICATION_CONTENT)
.setNotificationDataUri(URI)
.build();
+ private static final ConversationStatus GAME_STATUS =
+ new ConversationStatus
+ .Builder(PERSON_TILE.getId(), ACTIVITY_GAME)
+ .setDescription(GAME_DESCRIPTION)
+ .build();
+ private static final ConversationStatus NEW_STORY_WITH_AVAILABILITY =
+ new ConversationStatus
+ .Builder(PERSON_TILE.getId(), ACTIVITY_NEW_STORY)
+ .setAvailability(AVAILABILITY_AVAILABLE)
+ .build();
private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
SHORTCUT_ID_1).setLongLabel(
- "name").setPerson(PERSON)
+ NAME).setPerson(PERSON)
.build();
private final ShortcutInfo mShortcutInfoWithoutPerson = new ShortcutInfo.Builder(mContext,
SHORTCUT_ID_1).setLongLabel(
- "name")
+ NAME)
.build();
private final Notification mNotification1 = new Notification.Builder(mContext, "test")
.setContentTitle("TEST_TITLE")
@@ -189,10 +218,12 @@
@Mock
private Context mMockContext;
@Mock
+ private PackageManager mPackageManager;
+ @Mock
private NotificationEntryManager mNotificationEntryManager;
@Before
- public void setUp() throws RemoteException {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
@@ -212,6 +243,12 @@
isNull())).thenReturn(mMockCursor);
when(mMockContext.getString(R.string.birthday_status)).thenReturn(
mContext.getString(R.string.birthday_status));
+ when(mMockContext.getString(R.string.basic_status)).thenReturn(
+ mContext.getString(R.string.basic_status));
+ when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mMockContext.getString(R.string.over_timestamp)).thenReturn(
+ mContext.getString(R.string.over_timestamp));
+ when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
when(mNotificationEntryManager.getVisibleNotifications())
.thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
}
@@ -621,6 +658,137 @@
any());
}
+ @Test
+ public void testCreateRemoteViewsWithLastInteractionTime() {
+ RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
+ PERSON_TILE_WITHOUT_NOTIFICATION, 0);
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ // Has last interaction.
+ TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+ assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+ // No availability.
+ View availability = result.findViewById(R.id.availability);
+ assertEquals(View.GONE, availability.getVisibility());
+ // No new story.
+ View personIcon = result.findViewById(R.id.person_icon_only);
+ View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+ assertEquals(View.VISIBLE, personIcon.getVisibility());
+ assertEquals(View.GONE, personIconWithStory.getVisibility());
+ // No status.
+ assertThat((View) result.findViewById(R.id.status)).isNull();
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithGameTypeOnlyIsIgnored() {
+ PeopleSpaceTile tileWithAvailabilityAndNewStory =
+ PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+ Arrays.asList(NEW_STORY_WITH_AVAILABILITY,
+ new ConversationStatus.Builder(
+ PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
+ ACTIVITY_GAME).build())).build();
+ RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
+ tileWithAvailabilityAndNewStory, 0);
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ // Has last interaction over status.
+ TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+ assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+ // Has availability.
+ View availability = result.findViewById(R.id.availability);
+ assertEquals(View.VISIBLE, availability.getVisibility());
+ // Has new story.
+ View personIcon = result.findViewById(R.id.person_icon_only);
+ View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+ assertEquals(View.GONE, personIcon.getVisibility());
+ assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
+ // No status.
+ assertThat((View) result.findViewById(R.id.status)).isNull();
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithBirthdayTypeOnlyIsNotIgnored() {
+ PeopleSpaceTile tileWithStatusTemplate =
+ PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+ Arrays.asList(
+ NEW_STORY_WITH_AVAILABILITY, new ConversationStatus.Builder(
+ PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
+ ACTIVITY_BIRTHDAY).build())).build();
+ RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
+ tileWithStatusTemplate, 0);
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ // Has availability.
+ View availability = result.findViewById(R.id.availability);
+ assertEquals(View.VISIBLE, availability.getVisibility());
+ // Has new story.
+ View personIcon = result.findViewById(R.id.person_icon_only);
+ View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+ assertEquals(View.GONE, personIcon.getVisibility());
+ assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
+ // Has status text from backup text.
+ TextView statusContent = (TextView) result.findViewById(R.id.status);
+ assertEquals(statusContent.getText(), mContext.getString(R.string.birthday_status));
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithStatusTemplate() {
+ PeopleSpaceTile tileWithStatusTemplate =
+ PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+ Arrays.asList(GAME_STATUS,
+ NEW_STORY_WITH_AVAILABILITY)).build();
+ RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
+ tileWithStatusTemplate, 0);
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ // Has availability.
+ View availability = result.findViewById(R.id.availability);
+ assertEquals(View.VISIBLE, availability.getVisibility());
+ // Has new story.
+ View personIcon = result.findViewById(R.id.person_icon_only);
+ View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+ assertEquals(View.GONE, personIcon.getVisibility());
+ assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
+ // Has status.
+ TextView statusContent = (TextView) result.findViewById(R.id.status);
+ assertEquals(statusContent.getText(), GAME_DESCRIPTION);
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithNotificationTemplate() {
+ PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder()
+ .setNotificationDataUri(null)
+ .setStatuses(Arrays.asList(GAME_STATUS,
+ NEW_STORY_WITH_AVAILABILITY)).build();
+ RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
+ tileWithStatusAndNotification, 0);
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ TextView subtext = (TextView) result.findViewById(R.id.subtext);
+ assertTrue(subtext.getText().toString().contains("weeks ago"));
+ // Has availability.
+ View availability = result.findViewById(R.id.availability);
+ assertEquals(View.VISIBLE, availability.getVisibility());
+ // Has new story.
+ View personIcon = result.findViewById(R.id.person_icon_only);
+ View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+ assertEquals(View.GONE, personIcon.getVisibility());
+ assertEquals(View.VISIBLE, personIconWithStory.getVisibility());
+ // Has notification content.
+ TextView statusContent = (TextView) result.findViewById(R.id.content);
+ assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+ }
+
private ConversationChannelWrapper getConversationChannelWrapper(String shortcutId,
boolean importantConversation, long lastInteractionTimestamp) throws Exception {
ConversationChannelWrapper convo = new ConversationChannelWrapper();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index fd0715b..862e374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -443,9 +443,18 @@
@Test
public void testGetNetworkLoggingMessage() {
- assertEquals(null, mFooter.getNetworkLoggingMessage(false));
+ // Test network logging message on a device with a device owner.
+ // Network traffic may be monitored on the device.
+ assertEquals(null, mFooter.getNetworkLoggingMessage(true, false));
assertEquals(mContext.getString(R.string.monitoring_description_management_network_logging),
- mFooter.getNetworkLoggingMessage(true));
+ mFooter.getNetworkLoggingMessage(true, true));
+
+ // Test network logging message on a device with a managed profile owner
+ // Network traffic may be monitored on the work profile.
+ assertEquals(null, mFooter.getNetworkLoggingMessage(false, false));
+ assertEquals(
+ mContext.getString(R.string.monitoring_description_managed_profile_network_logging),
+ mFooter.getNetworkLoggingMessage(false, true));
}
@Test
diff --git a/services/Android.bp b/services/Android.bp
index 61591c2..3154628 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -33,6 +33,7 @@
":services.startop.iorap-sources",
":services.systemcaptions-sources",
":services.translation-sources",
+ ":services.texttospeech-sources",
":services.usage-sources",
":services.usb-sources",
":services.voiceinteraction-sources",
@@ -83,6 +84,7 @@
"services.startop",
"services.systemcaptions",
"services.translation",
+ "services.texttospeech",
"services.usage",
"services.usb",
"services.voiceinteraction",
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index eff410c..809304b 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2624,12 +2624,26 @@
info.minWidth = value != null ? value.data : 0;
value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight);
info.minHeight = value != null ? value.data : 0;
+
value = sa.peekValue(
com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeWidth);
info.minResizeWidth = value != null ? value.data : info.minWidth;
value = sa.peekValue(
com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeHeight);
info.minResizeHeight = value != null ? value.data : info.minHeight;
+
+ value = sa.peekValue(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_maxResizeWidth);
+ info.maxResizeWidth = value != null ? value.data : 0;
+ value = sa.peekValue(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_maxResizeHeight);
+ info.maxResizeHeight = value != null ? value.data : 0;
+
+ info.targetCellWidth = sa.getInt(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_targetCellWidth, 0);
+ info.targetCellHeight = sa.getInt(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_targetCellHeight, 0);
+
info.updatePeriodMillis = sa.getInt(
com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0);
info.initialLayout = sa.getResourceId(
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9f36910..0725bb2 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -127,7 +127,7 @@
"android.hardware.health-V2.0-java",
"android.hardware.health-V2.1-java",
"android.hardware.light-V1-java",
- "android.hardware.tv.cec-V1.0-java",
+ "android.hardware.tv.cec-V1.1-java",
"android.hardware.weaver-V1.0-java",
"android.hardware.biometrics.face-V1-java",
"android.hardware.biometrics.face-V1.0-java",
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 7f0b962..edaf6a90 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -105,8 +105,6 @@
private static final String TAG = "SensorPrivacyService";
- private static final int SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000;
-
/** 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
@@ -756,10 +754,7 @@
suppressPackageReminderTokens.add(token);
} else {
- mHandler.postDelayed(PooledLambda.obtainRunnable(
- SensorPrivacyServiceImpl::removeSuppressPackageReminderToken,
- this, key, token),
- SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS);
+ mHandler.removeSuppressPackageReminderToken(key, token);
}
}
}
@@ -1110,6 +1105,13 @@
}
listeners.finishBroadcast();
}
+
+ public void removeSuppressPackageReminderToken(Pair<String, UserHandle> key,
+ IBinder token) {
+ sendMessage(PooledLambda.obtainMessage(
+ SensorPrivacyServiceImpl::removeSuppressPackageReminderToken,
+ mSensorPrivacyServiceImpl, key, token));
+ }
}
private final class DeathRecipient implements IBinder.DeathRecipient {
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 27210da..ed4e1d9 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -22,6 +22,7 @@
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -29,6 +30,7 @@
import android.net.NetworkCapabilities;
import android.net.TelephonyNetworkSpecifier;
import android.net.vcn.IVcnManagementService;
+import android.net.vcn.IVcnStatusCallback;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
@@ -54,6 +56,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.LocationPermissionChecker;
import com.android.server.vcn.TelephonySubscriptionTracker;
import com.android.server.vcn.Vcn;
import com.android.server.vcn.VcnContext;
@@ -124,6 +127,7 @@
*
* @hide
*/
+// TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity
public class VcnManagementService extends IVcnManagementService.Stub {
@NonNull private static final String TAG = VcnManagementService.class.getSimpleName();
@@ -147,6 +151,9 @@
@NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
@NonNull private final VcnContext mVcnContext;
+ /** Can only be assigned when {@link #systemReady()} is called, since it uses AppOpsManager. */
+ @Nullable private LocationPermissionChecker mLocationPermissionChecker;
+
@GuardedBy("mLock")
@NonNull
private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>();
@@ -169,6 +176,10 @@
private final Map<IBinder, PolicyListenerBinderDeath> mRegisteredPolicyListeners =
new ArrayMap<>();
+ @GuardedBy("mLock")
+ @NonNull
+ private final Map<IBinder, VcnStatusCallbackInfo> mRegisteredStatusCallbacks = new ArrayMap<>();
+
@VisibleForTesting(visibility = Visibility.PRIVATE)
VcnManagementService(@NonNull Context context, @NonNull Dependencies deps) {
mContext = requireNonNull(context, "Missing context");
@@ -293,8 +304,8 @@
@NonNull ParcelUuid subscriptionGroup,
@NonNull VcnConfig config,
@NonNull TelephonySubscriptionSnapshot snapshot,
- @NonNull VcnSafemodeCallback safemodeCallback) {
- return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safemodeCallback);
+ @NonNull VcnSafeModeCallback safeModeCallback) {
+ return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safeModeCallback);
}
/** Gets the subId indicated by the given {@link WifiInfo}. */
@@ -302,6 +313,11 @@
// TODO(b/178501049): use the subId indicated by WifiInfo#getSubscriptionId
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
+
+ /** Creates a new LocationPermissionChecker for the provided Context. */
+ public LocationPermissionChecker newLocationPermissionChecker(@NonNull Context context) {
+ return new LocationPermissionChecker(context);
+ }
}
/** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -309,6 +325,7 @@
mContext.getSystemService(ConnectivityManager.class)
.registerNetworkProvider(mNetworkProvider);
mTelephonySubscriptionTracker.register();
+ mLocationPermissionChecker = mDeps.newLocationPermissionChecker(mVcnContext.getContext());
}
private void enforcePrimaryUser() {
@@ -440,12 +457,12 @@
// TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
// VCN.
- final VcnSafemodeCallbackImpl safemodeCallback =
- new VcnSafemodeCallbackImpl(subscriptionGroup);
+ final VcnSafeModeCallbackImpl safeModeCallback =
+ new VcnSafeModeCallbackImpl(subscriptionGroup);
final Vcn newInstance =
mDeps.newVcn(
- mVcnContext, subscriptionGroup, config, mLastSnapshot, safemodeCallback);
+ mVcnContext, subscriptionGroup, config, mLastSnapshot, safeModeCallback);
mVcns.put(subscriptionGroup, newInstance);
// Now that a new VCN has started, notify all registered listeners to refresh their
@@ -551,6 +568,14 @@
}
}
+ /** Get current VcnStatusCallbacks for testing purposes. */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public Map<IBinder, VcnStatusCallbackInfo> getAllStatusCallbacks() {
+ synchronized (mLock) {
+ return Collections.unmodifiableMap(mRegisteredStatusCallbacks);
+ }
+ }
+
/** Binder death recipient used to remove a registered policy listener. */
private class PolicyListenerBinderDeath implements Binder.DeathRecipient {
@NonNull private final IVcnUnderlyingNetworkPolicyListener mListener;
@@ -672,22 +697,109 @@
return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
}
- /** Callback for signalling when a Vcn has entered Safemode. */
- public interface VcnSafemodeCallback {
- /** Called by a Vcn to signal that it has entered Safemode. */
- void onEnteredSafemode();
+ /** Binder death recipient used to remove registered VcnStatusCallbacks. */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ class VcnStatusCallbackInfo implements Binder.DeathRecipient {
+ @NonNull final ParcelUuid mSubGroup;
+ @NonNull final IVcnStatusCallback mCallback;
+ @NonNull final String mPkgName;
+ final int mUid;
+
+ private VcnStatusCallbackInfo(
+ @NonNull ParcelUuid subGroup,
+ @NonNull IVcnStatusCallback callback,
+ @NonNull String pkgName,
+ int uid) {
+ mSubGroup = subGroup;
+ mCallback = callback;
+ mPkgName = pkgName;
+ mUid = uid;
+ }
+
+ @Override
+ public void binderDied() {
+ Log.e(TAG, "app died without unregistering VcnStatusCallback");
+ unregisterVcnStatusCallback(mCallback);
+ }
}
- /** VcnSafemodeCallback is used by Vcns to notify VcnManagementService on entering Safemode. */
- private class VcnSafemodeCallbackImpl implements VcnSafemodeCallback {
+ /** Registers the provided callback for receiving VCN status updates. */
+ @Override
+ public void registerVcnStatusCallback(
+ @NonNull ParcelUuid subGroup,
+ @NonNull IVcnStatusCallback callback,
+ @NonNull String opPkgName) {
+ final int callingUid = mDeps.getBinderCallingUid();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ requireNonNull(subGroup, "subGroup must not be null");
+ requireNonNull(callback, "callback must not be null");
+ requireNonNull(opPkgName, "opPkgName must not be null");
+
+ mContext.getSystemService(AppOpsManager.class).checkPackage(callingUid, opPkgName);
+
+ final IBinder cbBinder = callback.asBinder();
+ final VcnStatusCallbackInfo cbInfo =
+ new VcnStatusCallbackInfo(
+ subGroup, callback, opPkgName, mDeps.getBinderCallingUid());
+
+ try {
+ cbBinder.linkToDeath(cbInfo, 0 /* flags */);
+ } catch (RemoteException e) {
+ // Remote binder already died - don't add to mRegisteredStatusCallbacks and exit
+ return;
+ }
+
+ synchronized (mLock) {
+ if (mRegisteredStatusCallbacks.containsKey(cbBinder)) {
+ throw new IllegalStateException(
+ "Attempting to register a callback that is already in use");
+ }
+
+ mRegisteredStatusCallbacks.put(cbBinder, cbInfo);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /** Unregisters the provided callback from receiving future VCN status updates. */
+ @Override
+ public void unregisterVcnStatusCallback(@NonNull IVcnStatusCallback callback) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ requireNonNull(callback, "callback must not be null");
+
+ final IBinder cbBinder = callback.asBinder();
+ synchronized (mLock) {
+ VcnStatusCallbackInfo cbInfo = mRegisteredStatusCallbacks.remove(cbBinder);
+
+ if (cbInfo != null) {
+ cbBinder.unlinkToDeath(cbInfo, 0 /* flags */);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
+ /** Callback for signalling when a Vcn has entered safe mode. */
+ public interface VcnSafeModeCallback {
+ /** Called by a Vcn to signal that it has entered safe mode. */
+ void onEnteredSafeMode();
+ }
+
+ /** VcnSafeModeCallback is used by Vcns to notify VcnManagementService on entering safe mode. */
+ private class VcnSafeModeCallbackImpl implements VcnSafeModeCallback {
@NonNull private final ParcelUuid mSubGroup;
- private VcnSafemodeCallbackImpl(@NonNull final ParcelUuid subGroup) {
+ private VcnSafeModeCallbackImpl(@NonNull final ParcelUuid subGroup) {
mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
}
@Override
- public void onEnteredSafemode() {
+ public void onEnteredSafeMode() {
synchronized (mLock) {
// Ignore if this subscription group doesn't exist anymore
if (!mVcns.containsKey(mSubGroup)) {
@@ -695,6 +807,27 @@
}
notifyAllPolicyListenersLocked();
+
+ // Notify all registered StatusCallbacks for this subGroup
+ for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
+ if (!mSubGroup.equals(cbInfo.mSubGroup)) {
+ continue;
+ }
+ if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(
+ mSubGroup, cbInfo.mPkgName)) {
+ continue;
+ }
+
+ if (!mLocationPermissionChecker.checkLocationPermission(
+ cbInfo.mPkgName,
+ "VcnStatusCallback" /* featureId */,
+ cbInfo.mUid,
+ null /* message */)) {
+ continue;
+ }
+
+ Binder.withCleanCallingIdentity(() -> cbInfo.mCallback.onEnteredSafeMode());
+ }
}
}
}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 76c3467..26ce0d7 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -61,7 +61,7 @@
private final Object mLock = new Object();
private final Handler mHandler;
@GuardedBy("mLock")
- private final ArrayMap<Integer, Settings> mSettings = new ArrayMap<>();
+ private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
public GameManagerService(Context context) {
this(context, createServiceThread().getLooper());
@@ -99,7 +99,7 @@
synchronized (mLock) {
removeMessages(WRITE_SETTINGS, msg.obj);
if (mSettings.containsKey(userId)) {
- Settings userSettings = mSettings.get(userId);
+ GameManagerSettings userSettings = mSettings.get(userId);
userSettings.writePersistentDataLocked();
}
}
@@ -123,7 +123,7 @@
removeMessages(WRITE_SETTINGS, msg.obj);
removeMessages(REMOVE_SETTINGS, msg.obj);
if (mSettings.containsKey(userId)) {
- final Settings userSettings = mSettings.get(userId);
+ final GameManagerSettings userSettings = mSettings.get(userId);
mSettings.remove(userId);
userSettings.writePersistentDataLocked();
}
@@ -190,7 +190,7 @@
if (!mSettings.containsKey(userId)) {
return GameManager.GAME_MODE_UNSUPPORTED;
}
- Settings userSettings = mSettings.get(userId);
+ GameManagerSettings userSettings = mSettings.get(userId);
return userSettings.getGameModeLocked(packageName);
}
}
@@ -211,7 +211,7 @@
if (!mSettings.containsKey(userId)) {
return;
}
- Settings userSettings = mSettings.get(userId);
+ GameManagerSettings userSettings = mSettings.get(userId);
userSettings.setGameModeLocked(packageName, gameMode);
final Message msg = mHandler.obtainMessage(WRITE_SETTINGS);
msg.obj = userId;
@@ -235,7 +235,8 @@
return;
}
- Settings userSettings = new Settings(Environment.getDataSystemDeDirectory(userId));
+ GameManagerSettings userSettings =
+ new GameManagerSettings(Environment.getDataSystemDeDirectory(userId));
mSettings.put(userId, userSettings);
userSettings.readPersistentDataLocked();
}
diff --git a/services/core/java/com/android/server/app/Settings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
similarity index 98%
rename from services/core/java/com/android/server/app/Settings.java
rename to services/core/java/com/android/server/app/GameManagerSettings.java
index ab367fb..3e32380 100644
--- a/services/core/java/com/android/server/app/Settings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -41,7 +41,7 @@
* Persists all GameService related settings.
* @hide
*/
-public class Settings {
+public class GameManagerSettings {
// The XML file follows the below format:
// <?xml>
@@ -63,7 +63,7 @@
// PackageName -> GameMode
private final ArrayMap<String, Integer> mGameModes = new ArrayMap<>();
- Settings(File dataDir) {
+ GameManagerSettings(File dataDir) {
mSystemDir = new File(dataDir, "system");
mSystemDir.mkdirs();
FileUtils.setPermissions(mSystemDir.toString(),
@@ -144,6 +144,7 @@
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
+ // Do nothing
}
if (type != XmlPullParser.START_TAG) {
Slog.wtf(GameManagerService.TAG,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 06bcada..1643ec1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -50,6 +50,7 @@
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.function.Predicate;
@@ -149,6 +150,12 @@
* returns {@code null}.
*/
static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) {
+ HdmiCecController controller = createWithNativeWrapper(service, new NativeWrapperImpl11(),
+ atomWriter);
+ if (controller != null) {
+ return controller;
+ }
+ HdmiLogger.warning("Unable to use cec@1.1");
return createWithNativeWrapper(service, new NativeWrapperImpl(), atomWriter);
}
@@ -312,7 +319,7 @@
}
/**
- * Return CEC version of the device.
+ * Return highest CEC version supported by this device.
*
* <p>Declared as package-private. accessed by {@link HdmiControlService} only.
*/
@@ -745,13 +752,202 @@
boolean nativeIsConnected(int port);
}
- private static final class NativeWrapperImpl implements NativeWrapper,
+ private static final class NativeWrapperImpl11 implements NativeWrapper,
IHwBinder.DeathRecipient, getPhysicalAddressCallback {
- private IHdmiCec mHdmiCec;
+ private android.hardware.tv.cec.V1_1.IHdmiCec mHdmiCec;
+ @Nullable private HdmiCecCallback mCallback;
+
private final Object mLock = new Object();
private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+
+ @Override
+ public String nativeInit() {
+ return (connectToHal() ? mHdmiCec.toString() : null);
+ }
+
+ boolean connectToHal() {
+ try {
+ mHdmiCec = android.hardware.tv.cec.V1_1.IHdmiCec.getService(true);
+ try {
+ mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Couldn't link to death : ", e);
+ }
+ } catch (RemoteException | NoSuchElementException e) {
+ HdmiLogger.error("Couldn't connect to cec@1.1", e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onValues(int result, short addr) {
+ if (result == Result.SUCCESS) {
+ synchronized (mLock) {
+ mPhysicalAddress = new Short(addr).intValue();
+ }
+ }
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) {
+ HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting");
+ connectToHal();
+ // Reconnect the callback
+ if (mCallback != null) {
+ setCallback(mCallback);
+ }
+ }
+ }
+
+ @Override
+ public void setCallback(HdmiCecCallback callback) {
+ mCallback = callback;
+ try {
+ mHdmiCec.setCallback_1_1(new HdmiCecCallback11(callback));
+ } catch (RemoteException e) {
+ HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
+ }
+ }
+
+ @Override
+ public int nativeSendCecCommand(int srcAddress, int dstAddress, byte[] body) {
+ android.hardware.tv.cec.V1_1.CecMessage message =
+ new android.hardware.tv.cec.V1_1.CecMessage();
+ message.initiator = srcAddress;
+ message.destination = dstAddress;
+ message.body = new ArrayList<>(body.length);
+ for (byte b : body) {
+ message.body.add(b);
+ }
+ try {
+ return mHdmiCec.sendMessage_1_1(message);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to send CEC message : ", e);
+ return SendMessageResult.FAIL;
+ }
+ }
+
+ @Override
+ public int nativeAddLogicalAddress(int logicalAddress) {
+ try {
+ return mHdmiCec.addLogicalAddress_1_1(logicalAddress);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to add a logical address : ", e);
+ return Result.FAILURE_INVALID_ARGS;
+ }
+ }
+
+ @Override
+ public void nativeClearLogicalAddress() {
+ try {
+ mHdmiCec.clearLogicalAddress();
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to clear logical address : ", e);
+ }
+ }
+
+ @Override
+ public int nativeGetPhysicalAddress() {
+ try {
+ mHdmiCec.getPhysicalAddress(this);
+ return mPhysicalAddress;
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get physical address : ", e);
+ return INVALID_PHYSICAL_ADDRESS;
+ }
+ }
+
+ @Override
+ public int nativeGetVersion() {
+ try {
+ return mHdmiCec.getCecVersion();
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get cec version : ", e);
+ return Result.FAILURE_UNKNOWN;
+ }
+ }
+
+ @Override
+ public int nativeGetVendorId() {
+ try {
+ return mHdmiCec.getVendorId();
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get vendor id : ", e);
+ return Result.FAILURE_UNKNOWN;
+ }
+ }
+
+ @Override
+ public HdmiPortInfo[] nativeGetPortInfos() {
+ try {
+ ArrayList<android.hardware.tv.cec.V1_0.HdmiPortInfo> hdmiPortInfos =
+ mHdmiCec.getPortInfo();
+ HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.size()];
+ int i = 0;
+ for (android.hardware.tv.cec.V1_0.HdmiPortInfo portInfo : hdmiPortInfos) {
+ hdmiPortInfo[i] = new HdmiPortInfo(portInfo.portId,
+ portInfo.type,
+ portInfo.physicalAddress,
+ portInfo.cecSupported,
+ false,
+ portInfo.arcSupported);
+ i++;
+ }
+ return hdmiPortInfo;
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get port information : ", e);
+ return null;
+ }
+ }
+
+ @Override
+ public void nativeSetOption(int flag, boolean enabled) {
+ try {
+ mHdmiCec.setOption(flag, enabled);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to set option : ", e);
+ }
+ }
+
+ @Override
+ public void nativeSetLanguage(String language) {
+ try {
+ mHdmiCec.setLanguage(language);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to set language : ", e);
+ }
+ }
+
+ @Override
+ public void nativeEnableAudioReturnChannel(int port, boolean flag) {
+ try {
+ mHdmiCec.enableAudioReturnChannel(port, flag);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to enable/disable ARC : ", e);
+ }
+ }
+
+ @Override
+ public boolean nativeIsConnected(int port) {
+ try {
+ return mHdmiCec.isConnected(port);
+ } catch (RemoteException e) {
+ HdmiLogger.error("Failed to get connection info : ", e);
+ return false;
+ }
+ }
+ }
+
+ private static final class NativeWrapperImpl implements NativeWrapper,
+ IHwBinder.DeathRecipient, getPhysicalAddressCallback {
+ private android.hardware.tv.cec.V1_0.IHdmiCec mHdmiCec;
@Nullable private HdmiCecCallback mCallback;
+ private final Object mLock = new Object();
+ private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+
@Override
public String nativeInit() {
return (connectToHal() ? mHdmiCec.toString() : null);
@@ -765,8 +961,8 @@
} catch (RemoteException e) {
HdmiLogger.error("Couldn't link to death : ", e);
}
- } catch (RemoteException e) {
- HdmiLogger.error("Couldn't get tv.cec service : ", e);
+ } catch (RemoteException | NoSuchElementException e) {
+ HdmiLogger.error("Couldn't connect to cec@1.0", e);
return false;
}
return true;
@@ -776,7 +972,7 @@
public void setCallback(@NonNull HdmiCecCallback callback) {
mCallback = callback;
try {
- mHdmiCec.setCallback(callback);
+ mHdmiCec.setCallback(new HdmiCecCallback10(callback));
} catch (RemoteException e) {
HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
}
@@ -931,20 +1127,69 @@
}
}
- final class HdmiCecCallback extends IHdmiCecCallback.Stub {
+ final class HdmiCecCallback {
+ public void onCecMessage(int initiator, int destination, byte[] body) {
+ runOnServiceThread(
+ () -> handleIncomingCecCommand(initiator, destination, body));
+ }
+
+ public void onHotplugEvent(int portId, boolean connected) {
+ runOnServiceThread(() -> handleHotplug(portId, connected));
+ }
+ }
+
+ private static final class HdmiCecCallback10 extends IHdmiCecCallback.Stub {
+ private final HdmiCecCallback mHdmiCecCallback;
+
+ HdmiCecCallback10(HdmiCecCallback hdmiCecCallback) {
+ mHdmiCecCallback = hdmiCecCallback;
+ }
+
@Override
public void onCecMessage(CecMessage message) throws RemoteException {
byte[] body = new byte[message.body.size()];
for (int i = 0; i < message.body.size(); i++) {
body[i] = message.body.get(i);
}
- runOnServiceThread(
- () -> handleIncomingCecCommand(message.initiator, message.destination, body));
+ mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
}
@Override
public void onHotplugEvent(HotplugEvent event) throws RemoteException {
- runOnServiceThread(() -> handleHotplug(event.portId, event.connected));
+ mHdmiCecCallback.onHotplugEvent(event.portId, event.connected);
+ }
+ }
+
+ private static final class HdmiCecCallback11
+ extends android.hardware.tv.cec.V1_1.IHdmiCecCallback.Stub {
+ private final HdmiCecCallback mHdmiCecCallback;
+
+ HdmiCecCallback11(HdmiCecCallback hdmiCecCallback) {
+ mHdmiCecCallback = hdmiCecCallback;
+ }
+
+ @Override
+ public void onCecMessage_1_1(android.hardware.tv.cec.V1_1.CecMessage message)
+ throws RemoteException {
+ byte[] body = new byte[message.body.size()];
+ for (int i = 0; i < message.body.size(); i++) {
+ body[i] = message.body.get(i);
+ }
+ mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
+ }
+
+ @Override
+ public void onCecMessage(CecMessage message) throws RemoteException {
+ byte[] body = new byte[message.body.size()];
+ for (int i = 0; i < message.body.size(); i++) {
+ body[i] = message.body.get(i);
+ }
+ mHdmiCecCallback.onCecMessage(message.initiator, message.destination, body);
+ }
+
+ @Override
+ public void onHotplugEvent(HotplugEvent event) throws RemoteException {
+ mHdmiCecCallback.onHotplugEvent(event.portId, event.connected);
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 04acd51..2ed8481 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -139,8 +139,12 @@
@ServiceThreadOnly
void toggleAndFollowTvPower() {
assertRunOnServiceThread();
- // Wake up Android framework to take over CEC control from the microprocessor.
- mService.wakeUp();
+ if (mService.getPowerManager().isInteractive()) {
+ mService.pauseActiveMediaSessions();
+ } else {
+ // Wake up Android framework to take over CEC control from the microprocessor.
+ mService.wakeUp();
+ }
mService.queryDisplayStatus(new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int status) {
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index c4c0f68..bd577f3 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -371,6 +371,9 @@
int durationMs) {
Slog.i(mTag, "setTemporaryService(" + userId + ") to " + componentName + " for "
+ durationMs + "ms");
+ if (mServiceNameResolver == null) {
+ return;
+ }
enforceCallingPermissionForManagement();
Objects.requireNonNull(componentName);
@@ -404,6 +407,9 @@
enforceCallingPermissionForManagement();
synchronized (mLock) {
+ if (mServiceNameResolver == null) {
+ return false;
+ }
final boolean changed = mServiceNameResolver.setDefaultServiceEnabled(userId, enabled);
if (!changed) {
if (verbose) {
@@ -434,6 +440,10 @@
public final boolean isDefaultServiceEnabled(@UserIdInt int userId) {
enforceCallingPermissionForManagement();
+ if (mServiceNameResolver == null) {
+ return false;
+ }
+
synchronized (mLock) {
return mServiceNameResolver.isDefaultServiceEnabled(userId);
}
@@ -958,6 +968,10 @@
public void onPackageModified(String packageName) {
if (verbose) Slog.v(mTag, "onPackageModified(): " + packageName);
+ if (mServiceNameResolver == null) {
+ return;
+ }
+
final int userId = getChangingUserId();
final String serviceName = mServiceNameResolver.getDefaultServiceName(userId);
if (serviceName == null) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 5eec315..6deb19a 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1424,6 +1424,8 @@
mAppForegroundHelper.onSystemReady();
mLocationPowerSaveModeHelper.onSystemReady();
mScreenInteractiveHelper.onSystemReady();
+ mDeviceStationaryHelper.onSystemReady();
+ mDeviceIdleHelper.onSystemReady();
if (mEmergencyCallHelper != null) {
mEmergencyCallHelper.onSystemReady();
diff --git a/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java
new file mode 100644
index 0000000..85de4bb
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java
@@ -0,0 +1,105 @@
+/*
+ * 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.location.contexthub;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+
+/**
+ * A class that manages a timer used to keep track of how much time is left before a
+ * {@link ContextHubClientBroker} has its authorization state changed with a nanoapp from
+ * DENIED_GRACE_PERIOD to DENIED. Much of this implementation is copied from
+ * {@link android.os.CountDownTimer} while adding the ability to specify the provided looper.
+ *
+ * @hide
+ */
+public class AuthStateDenialTimer {
+ private static final long TIMEOUT_MS = SECONDS.toMillis(60);
+
+ private final ContextHubClientBroker mClient;
+ private final long mNanoAppId;
+ private final Handler mHandler;
+
+ /**
+ * Indicates when the timer should stop in the future.
+ */
+ private long mStopTimeInFuture;
+
+ /**
+ * boolean representing if the timer was cancelled
+ */
+ private boolean mCancelled = false;
+
+ public AuthStateDenialTimer(ContextHubClientBroker client, long nanoAppId, Looper looper) {
+ mClient = client;
+ mNanoAppId = nanoAppId;
+ mHandler = new CountDownHandler(looper);
+ }
+
+ /**
+ * Cancel the countdown.
+ */
+ public synchronized void cancel() {
+ mCancelled = true;
+ mHandler.removeMessages(MSG);
+ }
+
+ /**
+ * Start the countdown.
+ */
+ public synchronized void start() {
+ mCancelled = false;
+ mStopTimeInFuture = SystemClock.elapsedRealtime() + TIMEOUT_MS;
+ mHandler.sendMessage(mHandler.obtainMessage(MSG));
+ }
+
+ /**
+ * Called when the timer has expired.
+ */
+ public void onFinish() {
+ mClient.handleAuthStateTimerExpiry(mNanoAppId);
+ }
+
+ // Message type used to trigger the timer.
+ private static final int MSG = 1;
+
+ private class CountDownHandler extends Handler {
+
+ CountDownHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ synchronized (AuthStateDenialTimer.this) {
+ if (mCancelled) {
+ return;
+ }
+ final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
+ if (millisLeft <= 0) {
+ onFinish();
+ } else {
+ sendMessageDelayed(obtainMessage(MSG), millisLeft);
+ }
+ }
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index d3c853d..6249a06 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -17,9 +17,13 @@
package com.android.server.location.contexthub;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED_GRACE_PERIOD;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_GRANTED;
import android.Manifest;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -30,19 +34,21 @@
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubClient;
import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.IContextHubTransactionCallback;
import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
import android.os.Binder;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.server.location.ClientBrokerProto;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.Iterator;
-import java.util.Set;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
@@ -52,14 +58,53 @@
* notification callbacks. This class implements the IContextHubClient object, and the implemented
* APIs must be thread-safe.
*
+ * Additionally, this class is responsible for enforcing permissions usage and attribution are
+ * handled appropriately for a given client. In general, this works as follows:
+ *
+ * Client sending a message to a nanoapp:
+ * 1) When initially sending a message to nanoapps, clients are by default in a grace period state
+ * which allows them to always send their first message to nanoapps. This is done to allow
+ * clients (especially callback clients) to reset their conection to the nanoapp if they are
+ * killed / restarted (e.g. following a permission revocation).
+ * 2) After the initial message is sent, a check of permissions state is performed. If the
+ * client doesn't have permissions to communicate, it is placed into the denied grace period
+ * state and notified so that it can clean up its communication before it is completely denied
+ * access.
+ * 3) For subsequent messages, the auth state is checked synchronously and messages are denied if
+ * the client is denied authorization
+ *
+ * Client receiving a message from a nanoapp:
+ * 1) If a nanoapp sends a message to the client, the authentication state is checked synchronously.
+ * If there has been no message between the two before, the auth state is assumed granted.
+ * 2) The broker then checks that the client has all permissions the nanoapp requires and attributes
+ * all permissions required to consume the message being sent. If both of those checks pass, then
+ * the message is delivered. Otherwise, it's dropped.
+ *
+ * Client losing or gaining permissions (callback client):
+ * 1) Clients are killed when they lose permissions. This will cause callback clients to completely
+ * disconnect from the service. When they are restarted, their initial message will still be
+ * be allowed through and their permissions will be rechecked at that time.
+ * 2) If they gain a permission, the broker will notify them if that permission allows them to
+ * communicate with a nanoapp again.
+ *
+ * Client losing or gaining permissions (PendingIntent client):
+ * 1) Unlike callback clients, PendingIntent clients are able to maintain their connection to the
+ * service when they are killed. In their case, they will receive notifications of the broker
+ * that they have been denied required permissions or gain required permissions.
+ *
* TODO: Consider refactoring this class via inheritance
*
* @hide
*/
public class ContextHubClientBroker extends IContextHubClient.Stub
- implements IBinder.DeathRecipient {
+ implements IBinder.DeathRecipient, AppOpsManager.OnOpChangedListener {
private static final String TAG = "ContextHubClientBroker";
+ /**
+ * Message used by noteOp when this client receives a message from a nanoapp.
+ */
+ private static final String RECEIVE_MSG_NOTE = "NanoappMessageDelivery ";
+
/*
* The context of the service.
*/
@@ -119,6 +164,26 @@
*/
private final String mPackage;
+ /**
+ * The PID associated with this client.
+ */
+ private final int mPid;
+
+ /**
+ * The UID associated with this client.
+ */
+ private final int mUid;
+
+ /**
+ * Manager used for noting permissions usage of this broker.
+ */
+ private final AppOpsManager mAppOpsManager;
+
+ /**
+ * Manager used to queue transactions to the context hub.
+ */
+ private final ContextHubTransactionManager mTransactionManager;
+
/*
* True if a PendingIntent has been cancelled.
*/
@@ -130,11 +195,44 @@
private final boolean mHasAccessContextHubPermission;
/*
- * The set of nanoapp IDs that represent the group of nanoapps this client has a messaging
- * channel with, i.e. has sent or received messages from this particular nanoapp.
+ * Map containing all nanoapps this client has a messaging channel with and whether it is
+ * allowed to communicate over that channel. A channel is defined to have been opened if the
+ * client has sent or received messages from the particular nanoapp.
*/
- private final Set<Long> mMessageChannelNanoappIdSet =
- Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
+ private final Map<Long, Integer> mMessageChannelNanoappIdMap =
+ new ConcurrentHashMap<Long, Integer>();
+
+ /**
+ * Map containing all nanoapps that have active auth state denial timers.
+ */
+ private final Map<Long, AuthStateDenialTimer> mNappToAuthTimerMap =
+ new ConcurrentHashMap<Long, AuthStateDenialTimer>();
+
+ /**
+ * Callback used to obtain the latest set of nanoapp permissions and verify this client has
+ * each nanoapps permissions granted.
+ */
+ private final IContextHubTransactionCallback mQueryPermsCallback =
+ new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onTransactionComplete(int result) {
+ }
+
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) {
+ Log.e(TAG, "Permissions query failed, but still received nanoapp state");
+ } else if (nanoAppStateList != null) {
+ for (NanoAppState state : nanoAppStateList) {
+ if (mMessageChannelNanoappIdMap.containsKey(state.getNanoAppId())) {
+ List<String> permissions = state.getNanoAppPermissions();
+ updateNanoAppAuthState(state.getNanoAppId(),
+ hasPermissions(permissions), false /* gracePeriodExpired */);
+ }
+ }
+ }
+ }
+ };
/*
* Helper class to manage registered PendingIntent requests from the client.
@@ -182,40 +280,57 @@
}
}
- /* package */ ContextHubClientBroker(
- Context context, IContextHubWrapper contextHubProxy,
+ private ContextHubClientBroker(Context context, IContextHubWrapper contextHubProxy,
ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
- short hostEndPointId, IContextHubClientCallback callback, String attributionTag) {
+ short hostEndPointId, IContextHubClientCallback callback, String attributionTag,
+ ContextHubTransactionManager transactionManager, PendingIntent pendingIntent,
+ long nanoAppId, String packageName) {
mContext = context;
mContextHubProxy = contextHubProxy;
mClientManager = clientManager;
mAttachedContextHubInfo = contextHubInfo;
mHostEndPointId = hostEndPointId;
mCallbackInterface = callback;
- mPendingIntentRequest = new PendingIntentRequest();
- mPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
+ if (pendingIntent == null) {
+ mPendingIntentRequest = new PendingIntentRequest();
+ } else {
+ mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
+ }
+ mPackage = packageName;
mAttributionTag = attributionTag;
+ mTransactionManager = transactionManager;
+ mPid = Binder.getCallingPid();
+ mUid = Binder.getCallingUid();
mHasAccessContextHubPermission = context.checkCallingPermission(
Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+
+ startMonitoringOpChanges();
+ }
+
+ /* package */ ContextHubClientBroker(
+ Context context, IContextHubWrapper contextHubProxy,
+ ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
+ short hostEndPointId, IContextHubClientCallback callback, String attributionTag,
+ ContextHubTransactionManager transactionManager, String packageName) {
+ this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId, callback,
+ attributionTag, transactionManager, null /* pendingIntent */, 0 /* nanoAppId */,
+ packageName);
}
/* package */ ContextHubClientBroker(
Context context, IContextHubWrapper contextHubProxy,
ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
short hostEndPointId, PendingIntent pendingIntent, long nanoAppId,
- String attributionTag) {
- mContext = context;
- mContextHubProxy = contextHubProxy;
- mClientManager = clientManager;
- mAttachedContextHubInfo = contextHubInfo;
- mHostEndPointId = hostEndPointId;
- mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
- mPackage = pendingIntent.getCreatorPackage();
- mAttributionTag = attributionTag;
+ String attributionTag, ContextHubTransactionManager transactionManager) {
+ this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId,
+ null /* callback */, attributionTag, transactionManager, pendingIntent, nanoAppId,
+ pendingIntent.getCreatorPackage());
+ }
- mHasAccessContextHubPermission = context.checkCallingPermission(
- Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
+ private void startMonitoringOpChanges() {
+ mAppOpsManager.startWatchingMode(AppOpsManager.OP_NONE, mPackage, this);
}
/**
@@ -229,18 +344,44 @@
public int sendMessageToNanoApp(NanoAppMessage message) {
ContextHubServiceUtil.checkPermissions(mContext);
+ int authState;
+ synchronized (mMessageChannelNanoappIdMap) {
+ // Default to the granted auth state. The true auth state will be checked async if it's
+ // not denied.
+ authState = mMessageChannelNanoappIdMap.getOrDefault(
+ message.getNanoAppId(), AUTHORIZATION_GRANTED);
+ if (authState == AUTHORIZATION_DENIED) {
+ return ContextHubTransaction.RESULT_FAILED_PERMISSION_DENIED;
+ }
+ }
+
int result;
if (isRegistered()) {
- mMessageChannelNanoappIdSet.add(message.getNanoAppId());
+ // Even though the auth state is currently not denied, query the nanoapp permissions
+ // async and verify that the host app currently holds all the requisite permissions.
+ // This can't be done synchronously due to the async query that needs to be performed to
+ // obtain the nanoapp permissions.
+ boolean initialNanoappMessage = false;
+ synchronized (mMessageChannelNanoappIdMap) {
+ if (mMessageChannelNanoappIdMap.get(message.getNanoAppId()) == null) {
+ mMessageChannelNanoappIdMap.put(message.getNanoAppId(), AUTHORIZATION_GRANTED);
+ initialNanoappMessage = true;
+ }
+ }
+
+ if (initialNanoappMessage) {
+ // Only check permissions the first time a nanoapp is queried since nanoapp
+ // permissions don't currently change at runtime. If the host permission changes
+ // later, that'll be checked by onOpChanged.
+ checkNanoappPermsAsync();
+ }
+
ContextHubMsg messageToNanoApp =
ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message);
int contextHubId = mAttachedContextHubInfo.getId();
try {
- // TODO(166846988): Fill in host permissions before sending a message.
- result = mContextHubProxy.sendMessageToHub(
- contextHubId, messageToNanoApp,
- new ArrayList<String>() /* hostPermissions */);
+ result = mContextHubProxy.sendMessageToHub(contextHubId, messageToNanoApp);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
+ contextHubId + ")", e);
@@ -275,6 +416,19 @@
onClientExit();
}
+ @Override
+ public void onOpChanged(String op, String packageName) {
+ if (packageName.equals(mPackage)) {
+ if (!mMessageChannelNanoappIdMap.isEmpty()) {
+ checkNanoappPermsAsync();
+ }
+ }
+ }
+
+ /* package */ String getPackageName() {
+ return mPackage;
+ }
+
/**
* Used to override the attribution tag with a newer value if a PendingIntent broker is
* retrieved.
@@ -308,15 +462,39 @@
* Sends a message to the client associated with this object.
*
* @param message the message that came from a nanoapp
+ * @param nanoappPermissions permissions required to communicate with the nanoapp sending this
+ * message
+ * @param messagePermissions permissions required to consume the message being delivered. These
+ * permissions are what will be attributed to the client through noteOp.
*/
- /* package */ void sendMessageToClient(NanoAppMessage message) {
- mMessageChannelNanoappIdSet.add(message.getNanoAppId());
+ /* package */ void sendMessageToClient(
+ NanoAppMessage message, List<String> nanoappPermissions,
+ List<String> messagePermissions) {
+ long nanoAppId = message.getNanoAppId();
+
+ int authState = mMessageChannelNanoappIdMap.getOrDefault(nanoAppId, AUTHORIZATION_GRANTED);
+
+ // If in the grace period, the host may not receive any messages containing permissions
+ // covered data.
+ if (authState == AUTHORIZATION_DENIED_GRACE_PERIOD && !messagePermissions.isEmpty()) {
+ Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
+ + " in grace period and napp msg has permissions");
+ return;
+ }
+
+ if (authState == AUTHORIZATION_DENIED || !hasPermissions(nanoappPermissions)
+ || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) {
+ Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
+ + " doesn't have permission");
+ return;
+ }
+
invokeCallback(callback -> callback.onMessageFromNanoApp(message));
Supplier<Intent> supplier =
- () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, message.getNanoAppId())
+ () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, nanoAppId)
.putExtra(ContextHubManager.EXTRA_MESSAGE, message);
- sendPendingIntent(supplier, message.getNanoAppId());
+ sendPendingIntent(supplier, nanoAppId);
}
/**
@@ -325,6 +503,10 @@
* @param nanoAppId the ID of the nanoapp that was loaded.
*/
/* package */ void onNanoAppLoaded(long nanoAppId) {
+ // Check the latest state to see if the loaded nanoapp's permissions changed such that the
+ // host app can communicate with it again.
+ checkNanoappPermsAsync();
+
invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId));
sendPendingIntent(
() -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId), nanoAppId);
@@ -392,6 +574,43 @@
}
/**
+ * Checks that this client has all of the provided permissions.
+ *
+ * @param permissions list of permissions to check
+ * @return true if the client has all of the permissions granted
+ */
+ /* package */ boolean hasPermissions(List<String> permissions) {
+ for (String permission : permissions) {
+ if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Attributes the provided permissions to the package of this client.
+ *
+ * @param permissions list of permissions covering data the client is about to receive
+ * @param noteMessage message that should be noted alongside permissions attribution to
+ * facilitate debugging
+ * @return true if client has ability to use all of the provided permissions
+ */
+ /* package */ boolean notePermissions(List<String> permissions, String noteMessage) {
+ for (String permission : permissions) {
+ int opCode = mAppOpsManager.permissionToOpCode(permission);
+ if (opCode != AppOpsManager.OP_NONE) {
+ if (mAppOpsManager.noteOp(opCode, mUid, mPackage, mAttributionTag, noteMessage)
+ != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
* @return true if the client is a PendingIntent client that has been cancelled.
*/
/* package */ boolean isPendingIntentCancelled() {
@@ -399,6 +618,101 @@
}
/**
+ * Handles timer expiry for a client whose auth state with a nanoapp was previously in the grace
+ * period.
+ */
+ /* package */ void handleAuthStateTimerExpiry(long nanoAppId) {
+ AuthStateDenialTimer timer;
+ synchronized (mMessageChannelNanoappIdMap) {
+ timer = mNappToAuthTimerMap.remove(nanoAppId);
+ }
+
+ if (timer != null) {
+ updateNanoAppAuthState(
+ nanoAppId, false /* hasPermissions */, true /* gracePeriodExpired */);
+ }
+ }
+
+ /**
+ * Verifies this client has the permissions to communicate with all of the nanoapps it has
+ * communicated with in the past.
+ */
+ private void checkNanoappPermsAsync() {
+ ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+ mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
+ mTransactionManager.addTransaction(transaction);
+ }
+
+ /**
+ * Updates the latest authentication state for this client to be able to communicate with the
+ * given nanoapp.
+ */
+ private void updateNanoAppAuthState(
+ long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired) {
+ updateNanoAppAuthState(
+ nanoAppId, hasPermissions, gracePeriodExpired, false /* forceDenied */);
+ }
+
+ /* package */ void updateNanoAppAuthState(
+ long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired,
+ boolean forceDenied) {
+ int curAuthState;
+ int newAuthState;
+ synchronized (mMessageChannelNanoappIdMap) {
+ curAuthState = mMessageChannelNanoappIdMap.getOrDefault(
+ nanoAppId, AUTHORIZATION_GRANTED);
+ newAuthState = curAuthState;
+ // The below logic ensures that only the following transitions are possible:
+ // GRANTED -> DENIED_GRACE_PERIOD only if permissions have been lost
+ // DENIED_GRACE_PERIOD -> DENIED only if the grace period expires
+ // DENIED/DENIED_GRACE_PERIOD -> GRANTED only if permissions are granted again
+ // any state -> DENIED if "forceDenied" is true
+ if (forceDenied) {
+ newAuthState = AUTHORIZATION_DENIED;
+ } else if (gracePeriodExpired) {
+ if (curAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) {
+ newAuthState = AUTHORIZATION_DENIED;
+ }
+ } else {
+ if (curAuthState == AUTHORIZATION_GRANTED && !hasPermissions) {
+ newAuthState = AUTHORIZATION_DENIED_GRACE_PERIOD;
+ } else if (curAuthState != AUTHORIZATION_GRANTED && hasPermissions) {
+ newAuthState = AUTHORIZATION_GRANTED;
+ }
+ }
+
+ if (newAuthState != AUTHORIZATION_DENIED_GRACE_PERIOD) {
+ AuthStateDenialTimer timer = mNappToAuthTimerMap.remove(nanoAppId);
+ if (timer != null) {
+ timer.cancel();
+ }
+ } else if (curAuthState == AUTHORIZATION_GRANTED) {
+ AuthStateDenialTimer timer =
+ new AuthStateDenialTimer(this, nanoAppId, Looper.getMainLooper());
+ mNappToAuthTimerMap.put(nanoAppId, timer);
+ timer.start();
+ }
+
+ if (curAuthState != newAuthState) {
+ mMessageChannelNanoappIdMap.put(nanoAppId, newAuthState);
+ }
+ }
+ if (curAuthState != newAuthState) {
+ // Don't send the callback in the synchronized block or it could end up in a deadlock.
+ sendAuthStateCallback(nanoAppId, newAuthState);
+ }
+ }
+
+ private void sendAuthStateCallback(long nanoAppId, int authState) {
+ invokeCallback(callback -> callback.onClientAuthorizationChanged(nanoAppId, authState));
+
+ Supplier<Intent> supplier =
+ () -> createIntent(ContextHubManager.EVENT_CLIENT_AUTHORIZATION, nanoAppId)
+ .putExtra(ContextHubManager.EXTRA_CLIENT_AUTHORIZATION_STATE, authState);
+ sendPendingIntent(supplier, nanoAppId);
+ }
+
+ /**
* Helper function to invoke a specified client callback, if the connection is open.
*
* @param consumer the consumer specifying the callback to invoke
@@ -507,6 +821,20 @@
mClientManager.unregisterClient(mHostEndPointId);
mRegistered = false;
}
+ mAppOpsManager.stopWatchingMode(this);
+ }
+
+ private String authStateToString(@ContextHubManager.AuthorizationState int state) {
+ switch (state) {
+ case AUTHORIZATION_DENIED:
+ return "DENIED";
+ case AUTHORIZATION_DENIED_GRACE_PERIOD:
+ return "DENIED_GRACE_PERIOD";
+ case AUTHORIZATION_GRANTED:
+ return "GRANTED";
+ default:
+ return "UNKNOWN";
+ }
}
/**
@@ -545,11 +873,14 @@
} else {
out += "package: " + mPackage;
}
- if (mMessageChannelNanoappIdSet.size() > 0) {
+ if (mMessageChannelNanoappIdMap.size() > 0) {
out += " messageChannelNanoappSet: (";
- Iterator<Long> it = mMessageChannelNanoappIdSet.iterator();
+ Iterator<Map.Entry<Long, Integer>> it =
+ mMessageChannelNanoappIdMap.entrySet().iterator();
while (it.hasNext()) {
- out += "0x" + Long.toHexString(it.next());
+ Map.Entry<Long, Integer> entry = it.next();
+ out += "0x" + Long.toHexString(entry.getKey()) + " auth state: "
+ + authStateToString(entry.getValue());
if (it.hasNext()) {
out += ",";
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
index 0351edb..e3522f6 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
@@ -36,6 +36,7 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
+import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
@@ -136,8 +137,7 @@
}
}
- /* package */ ContextHubClientManager(
- Context context, IContextHubWrapper contextHubProxy) {
+ /* package */ ContextHubClientManager(Context context, IContextHubWrapper contextHubProxy) {
mContext = context;
mContextHubProxy = contextHubProxy;
}
@@ -155,13 +155,15 @@
*/
/* package */ IContextHubClient registerClient(
ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback,
- String attributionTag) {
+ String attributionTag, ContextHubTransactionManager transactionManager,
+ String packageName) {
ContextHubClientBroker broker;
synchronized (this) {
short hostEndPointId = getHostEndPointId();
broker = new ContextHubClientBroker(
mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
- hostEndPointId, clientCallback, attributionTag);
+ hostEndPointId, clientCallback, attributionTag, transactionManager,
+ packageName);
mHostEndPointIdToClientMap.put(hostEndPointId, broker);
mRegistrationRecordDeque.add(
new RegistrationRecord(broker.toString(), ACTION_REGISTERED));
@@ -194,7 +196,7 @@
*/
/* package */ IContextHubClient registerClient(
ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId,
- String attributionTag) {
+ String attributionTag, ContextHubTransactionManager transactionManager) {
ContextHubClientBroker broker;
String registerString = "Regenerated";
synchronized (this) {
@@ -204,7 +206,8 @@
short hostEndPointId = getHostEndPointId();
broker = new ContextHubClientBroker(
mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
- hostEndPointId, pendingIntent, nanoAppId, attributionTag);
+ hostEndPointId, pendingIntent, nanoAppId, attributionTag,
+ transactionManager);
mHostEndPointIdToClientMap.put(hostEndPointId, broker);
registerString = "Registered";
mRegistrationRecordDeque.add(
@@ -224,9 +227,14 @@
* Handles a message sent from a nanoapp.
*
* @param contextHubId the ID of the hub where the nanoapp sent the message from
- * @param message the message send by a nanoapp
+ * @param message the message send by a nanoapp
+ * @param nanoappPermissions the set of permissions the nanoapp holds
+ * @param messagePermissions the set of permissions that should be used for attributing
+ * permissions when this message is consumed by a client
*/
- /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) {
+ /* package */ void onMessageFromNanoApp(
+ int contextHubId, ContextHubMsg message, List<String> nanoappPermissions,
+ List<String> messagePermissions) {
NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message);
if (DEBUG_LOG_ENABLED) {
@@ -234,11 +242,19 @@
}
if (clientMessage.isBroadcastMessage()) {
- broadcastMessage(contextHubId, clientMessage);
+ // Broadcast messages shouldn't be sent with any permissions tagged per CHRE API
+ // requirements.
+ if (!messagePermissions.isEmpty()) {
+ Log.wtf(TAG, "Received broadcast message with permissions from " + message.appName);
+ }
+
+ broadcastMessage(
+ contextHubId, clientMessage, nanoappPermissions, messagePermissions);
} else {
ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint);
if (proxy != null) {
- proxy.sendMessageToClient(clientMessage);
+ proxy.sendMessageToClient(
+ clientMessage, nanoappPermissions, messagePermissions);
} else {
Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = "
+ message.hostEndPoint + ")");
@@ -303,6 +319,21 @@
}
/**
+ * Runs a command for each client that is attached to a hub with the given ID.
+ *
+ * @param contextHubId the ID of the hub
+ * @param callback the command to invoke for the client
+ */
+ /* package */ void forEachClientOfHub(
+ int contextHubId, Consumer<ContextHubClientBroker> callback) {
+ for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
+ if (broker.getAttachedContextHubId() == contextHubId) {
+ callback.accept(broker);
+ }
+ }
+ }
+
+ /**
* Returns an available host endpoint ID.
*
* @returns an available host endpoint ID
@@ -333,22 +364,12 @@
* @param contextHubId the ID of the hub where the nanoapp sent the message from
* @param message the message send by a nanoapp
*/
- private void broadcastMessage(int contextHubId, NanoAppMessage message) {
- forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message));
- }
-
- /**
- * Runs a command for each client that is attached to a hub with the given ID.
- *
- * @param contextHubId the ID of the hub
- * @param callback the command to invoke for the client
- */
- private void forEachClientOfHub(int contextHubId, Consumer<ContextHubClientBroker> callback) {
- for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
- if (broker.getAttachedContextHubId() == contextHubId) {
- callback.accept(broker);
- }
- }
+ private void broadcastMessage(
+ int contextHubId, NanoAppMessage message, List<String> nanoappPermissions,
+ List<String> messagePermissions) {
+ forEachClientOfHub(contextHubId,
+ client -> client.sendMessageToClient(
+ message, nanoappPermissions, messagePermissions));
}
/**
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 2eafe6a..0737db7 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -50,6 +50,8 @@
import android.os.Binder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -64,6 +66,7 @@
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -98,6 +101,7 @@
private final Context mContext;
private final Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
+ private final List<String> mSupportedContextHubPerms;
private final List<ContextHubInfo> mContextHubInfoList;
private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
new RemoteCallbackList<>();
@@ -140,7 +144,7 @@
public void handleClientMsg(ContextHubMsg message) {
handleClientMessageCallback(mContextHubId, message,
Collections.emptyList() /* nanoappPermissions */,
- Collections.emptyList() /* messageContentPermissions */);
+ Collections.emptyList() /* messagePermissions */);
}
@Override
@@ -167,9 +171,9 @@
@Override
public void handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message,
- ArrayList<String> messageContentPermissions) {
+ ArrayList<String> messagePermissions) {
handleClientMessageCallback(mContextHubId, message.msg_1_0, message.permissions,
- messageContentPermissions);
+ messagePermissions);
}
@Override
@@ -187,14 +191,11 @@
mClientManager = null;
mDefaultClientMap = Collections.emptyMap();
mContextHubIdToInfoMap = Collections.emptyMap();
+ mSupportedContextHubPerms = Collections.emptyList();
mContextHubInfoList = Collections.emptyList();
return;
}
- mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
- mTransactionManager = new ContextHubTransactionManager(
- mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
-
Pair<List<ContextHub>, List<String>> hubInfo;
try {
hubInfo = mContextHubWrapper.getHubs();
@@ -202,16 +203,21 @@
Log.e(TAG, "RemoteException while getting Context Hub info", e);
hubInfo = new Pair(Collections.emptyList(), Collections.emptyList());
}
+
mContextHubIdToInfoMap = Collections.unmodifiableMap(
ContextHubServiceUtil.createContextHubInfoMap(hubInfo.first));
+ mSupportedContextHubPerms = hubInfo.second;
mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
+ mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
+ mTransactionManager = new ContextHubTransactionManager(
+ mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
IContextHubClient client = mClientManager.registerClient(
contextHubInfo, createDefaultClientCallback(contextHubId),
- null /* attributionTag */);
+ null /* attributionTag */, mTransactionManager, mContext.getPackageName());
defaultClientMap.put(contextHubId, client);
try {
@@ -362,6 +368,12 @@
}
@Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver result) {
+ new ContextHubShellCommand(mContext, this).exec(this, in, out, err, args, callback, result);
+ }
+
+ @Override
public int registerCallback(IContextHubCallback callback) throws RemoteException {
checkPermissions();
mCallbacksList.register(callback);
@@ -611,13 +623,15 @@
/**
* Handles a unicast or broadcast message from a nanoapp.
*
- * @param contextHubId the ID of the hub the message came from
- * @param message the message contents
+ * @param contextHubId the ID of the hub the message came from
+ * @param message the message contents
+ * @param reqPermissions the permissions required to consume this message
*/
private void handleClientMessageCallback(
int contextHubId, ContextHubMsg message, List<String> nanoappPermissions,
- List<String> messageContentPermissions) {
- mClientManager.onMessageFromNanoApp(contextHubId, message);
+ List<String> messagePermissions) {
+ mClientManager.onMessageFromNanoApp(
+ contextHubId, message, nanoappPermissions, messagePermissions);
}
/**
@@ -723,6 +737,7 @@
* @param contextHubId the ID of the hub this client is attached to
* @param clientCallback the client interface to register with the service
* @param attributionTag an optional attribution tag within the given package
+ * @param packageName the name of the package creating this client
* @return the generated client interface, null if registration was unsuccessful
* @throws IllegalArgumentException if contextHubId is not a valid ID
* @throws IllegalStateException if max number of clients have already registered
@@ -731,7 +746,7 @@
@Override
public IContextHubClient createClient(
int contextHubId, IContextHubClientCallback clientCallback,
- @Nullable String attributionTag) throws RemoteException {
+ @Nullable String attributionTag, String packageName) throws RemoteException {
checkPermissions();
if (!isValidContextHubId(contextHubId)) {
throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
@@ -741,7 +756,8 @@
}
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
- return mClientManager.registerClient(contextHubInfo, clientCallback, attributionTag);
+ return mClientManager.registerClient(
+ contextHubInfo, clientCallback, attributionTag, mTransactionManager, packageName);
}
/**
@@ -766,7 +782,7 @@
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
return mClientManager.registerClient(
- contextHubInfo, pendingIntent, nanoAppId, attributionTag);
+ contextHubInfo, pendingIntent, nanoAppId, attributionTag, mTransactionManager);
}
/**
@@ -907,6 +923,8 @@
for (ContextHubInfo hubInfo : mContextHubIdToInfoMap.values()) {
pw.println(hubInfo);
}
+ pw.println("Supported permissions: "
+ + Arrays.toString(mSupportedContextHubPerms.toArray()));
pw.println("");
pw.println("=================== NANOAPPS ====================");
// Dump nanoAppHash
@@ -923,6 +941,16 @@
// dump eventLog
}
+ /* package */ void denyClientAuthState(int contextHubId, String packageName, long nanoAppId) {
+ mClientManager.forEachClientOfHub(contextHubId, client -> {
+ if (client.getPackageName().equals(packageName)) {
+ client.updateNanoAppAuthState(
+ nanoAppId, false /* hasPermissions */, false /* gracePeriodExpired */,
+ true /* forceDenied */);
+ }
+ });
+ }
+
private void dump(ProtoOutputStream proto) {
mContextHubIdToInfoMap.values().forEach(hubInfo -> {
long token = proto.start(ContextHubServiceProto.CONTEXT_HUB_INFO);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java b/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java
new file mode 100644
index 0000000..5ec85e6
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubShellCommand.java
@@ -0,0 +1,71 @@
+/*
+ * 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.location.contexthub;
+
+import android.content.Context;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * ShellCommands for ContextHubService.
+ *
+ * Use with {@code adb shell cmd contexthub ...}.
+ *
+ * @hide
+ */
+public class ContextHubShellCommand extends ShellCommand {
+
+ // Internal service impl -- must perform security checks before touching.
+ private final ContextHubService mInternal;
+
+ public ContextHubShellCommand(Context context, ContextHubService service) {
+ mInternal = service;
+
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_CONTEXT_HUB, "ContextHubShellCommand");
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if ("deny".equals(cmd)) {
+ return runDisableAuth();
+ }
+
+ return handleDefaultCommands(cmd);
+ }
+
+ private int runDisableAuth() {
+ int contextHubId = Integer.decode(getNextArgRequired());
+ String packageName = getNextArgRequired();
+ long nanoAppId = Long.decode(getNextArgRequired());
+
+ mInternal.denyClientAuthState(contextHubId, packageName, nanoAppId);
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("ContextHub commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" deny [contextHubId] [packageName] [nanoAppId]");
+ pw.println(" Immediately transitions the package's authentication state to denied so");
+ pw.println(" can no longer communciate with the nanoapp.");
+ }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index c1d63dd..3a5c220 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -106,9 +106,8 @@
/**
* Calls the appropriate sendMessageToHub function depending on the HAL version.
*/
- public abstract int sendMessageToHub(
- int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
- ArrayList<String> hostPermissions) throws RemoteException;
+ public abstract int sendMessageToHub(int hubId,
+ android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException;
/**
* @return A valid instance of Contexthub HAL 1.0.
@@ -181,9 +180,8 @@
mHub.registerCallback(hubId, callback);
}
- public int sendMessageToHub(
- int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
- ArrayList<String> hostPermissions) throws RemoteException {
+ public int sendMessageToHub(int hubId,
+ android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
return mHub.sendMessageToHub(hubId, message);
}
@@ -236,9 +234,8 @@
mHub.registerCallback(hubId, callback);
}
- public int sendMessageToHub(
- int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
- ArrayList<String> hostPermissions) throws RemoteException {
+ public int sendMessageToHub(int hubId,
+ android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
return mHub.sendMessageToHub(hubId, message);
}
@@ -307,13 +304,11 @@
mHub.registerCallback_1_2(hubId, callback);
}
- public int sendMessageToHub(
- int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
- ArrayList<String> hostPermissions) throws RemoteException {
+ public int sendMessageToHub(int hubId,
+ android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
android.hardware.contexthub.V1_2.ContextHubMsg newMessage =
new android.hardware.contexthub.V1_2.ContextHubMsg();
newMessage.msg_1_0 = message;
- newMessage.permissions = hostPermissions;
return mHub.sendMessageToHub_1_2(hubId, newMessage);
}
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
index 6a89079..736b654 100644
--- a/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
@@ -21,50 +21,80 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Binder;
import android.os.PowerManager;
+import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
+import java.util.Objects;
+
/**
* Provides accessors and listeners for device stationary state.
*/
public class SystemDeviceIdleHelper extends DeviceIdleHelper {
private final Context mContext;
- private final PowerManager mPowerManager;
+ private PowerManager mPowerManager;
+
+ private boolean mSystemReady;
+ private boolean mRegistrationRequired;
private @Nullable BroadcastReceiver mReceiver;
public SystemDeviceIdleHelper(Context context) {
mContext = context;
- mPowerManager = context.getSystemService(PowerManager.class);
+ }
+
+ public synchronized void onSystemReady() {
+ mSystemReady = true;
+ mPowerManager = Objects.requireNonNull(mContext.getSystemService(PowerManager.class));
+ onRegistrationStateChanged();
}
@Override
- protected void registerInternal() {
- if (mReceiver == null) {
- mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- notifyDeviceIdleChanged();
- }
- };
+ protected synchronized void registerInternal() {
+ mRegistrationRequired = true;
+ onRegistrationStateChanged();
+ }
- mContext.registerReceiver(mReceiver,
- new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
- FgThread.getHandler());
+ @Override
+ protected synchronized void unregisterInternal() {
+ mRegistrationRequired = false;
+ onRegistrationStateChanged();
+ }
+
+ private void onRegistrationStateChanged() {
+ if (!mSystemReady) {
+ return;
}
- }
- @Override
- protected void unregisterInternal() {
- if (mReceiver != null) {
- mContext.unregisterReceiver(mReceiver);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (mRegistrationRequired && mReceiver == null) {
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ notifyDeviceIdleChanged();
+ }
+ };
+ mContext.registerReceiver(receiver,
+ new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
+ FgThread.getHandler());
+ mReceiver = receiver;
+ } else if (!mRegistrationRequired && mReceiver != null) {
+ BroadcastReceiver receiver = mReceiver;
+ mReceiver = null;
+ mContext.unregisterReceiver(receiver);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
@Override
public boolean isDeviceIdle() {
+ Preconditions.checkState(mPowerManager != null);
return mPowerManager.isDeviceIdleMode();
}
}
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
index 6f0e681..9874ecf 100644
--- a/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
@@ -16,6 +16,9 @@
package com.android.server.location.injector;
+import android.os.Binder;
+
+import com.android.internal.util.Preconditions;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
@@ -26,19 +29,35 @@
*/
public class SystemDeviceStationaryHelper extends DeviceStationaryHelper {
- private final DeviceIdleInternal mDeviceIdle;
+ private DeviceIdleInternal mDeviceIdle;
- public SystemDeviceStationaryHelper() {
+ public SystemDeviceStationaryHelper() {}
+
+ public void onSystemReady() {
mDeviceIdle = Objects.requireNonNull(LocalServices.getService(DeviceIdleInternal.class));
}
@Override
public void addListener(DeviceIdleInternal.StationaryListener listener) {
- mDeviceIdle.registerStationaryListener(listener);
+ Preconditions.checkState(mDeviceIdle != null);
+
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mDeviceIdle.registerStationaryListener(listener);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void removeListener(DeviceIdleInternal.StationaryListener listener) {
- mDeviceIdle.unregisterStationaryListener(listener);
+ Preconditions.checkState(mDeviceIdle != null);
+
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mDeviceIdle.unregisterStationaryListener(listener);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
diff --git a/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java b/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java
index 0d7bcb0..03ade5f 100644
--- a/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemScreenInteractiveHelper.java
@@ -68,7 +68,7 @@
mReady = true;
}
- private void onScreenInteractiveChanged(boolean interactive) {
+ void onScreenInteractiveChanged(boolean interactive) {
if (interactive == mIsInteractive) {
return;
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 905733c..dbd1211 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1341,7 +1341,9 @@
}
public void forgetAllPackageInfos(final int userId) {
- for (int i = 0, n = mCache.size(); i < n; i++) {
+ // Iterate in reverse order since removing the package in all users will remove the
+ // package from the cache.
+ for (int i = mCache.size() - 1; i >= 0; i--) {
removePackageUser(mCache.valueAt(i), userId);
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index fb183f5..799ab46 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -244,7 +244,11 @@
@NonNull
Set<PackageAndUser> onPackageAdded(@NonNull final String pkgName,
final int userId) throws OperationFailedException {
- return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
+ final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+ // Always update the overlays of newly added packages.
+ updatedTargets.add(new PackageAndUser(pkgName, userId));
+ updatedTargets.addAll(reconcileSettingsForPackage(pkgName, userId, 0 /* flags */));
+ return updatedTargets;
}
@NonNull
@@ -282,7 +286,7 @@
private Set<PackageAndUser> removeOverlaysForUser(
@NonNull final Predicate<OverlayInfo> condition, final int userId) {
final List<OverlayInfo> overlays = mSettings.removeIf(
- io -> userId == io.userId && condition.test(io) );
+ io -> userId == io.userId && condition.test(io));
Set<PackageAndUser> targets = Collections.emptySet();
for (int i = 0, n = overlays.size(); i < n; i++) {
final OverlayInfo info = overlays.get(i);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index b06e84d..d3a56c6 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -22,6 +22,7 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
+import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1170,6 +1171,8 @@
ret = ShortcutInfo.FLAG_CACHED_NOTIFICATIONS;
} else if (cacheFlags == FLAG_CACHE_BUBBLE_SHORTCUTS) {
ret = ShortcutInfo.FLAG_CACHED_BUBBLES;
+ } else if (cacheFlags == FLAG_CACHE_PEOPLE_TILE_SHORTCUTS) {
+ ret = ShortcutInfo.FLAG_CACHED_PEOPLE_TILE;
}
Preconditions.checkArgumentPositive(ret, "Invalid cache owner");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 753f22d..24c27be 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1522,6 +1522,14 @@
}
@Override
+ public boolean isUserForeground() {
+ int callingUserId = Binder.getCallingUserHandle().getIdentifier();
+ int currentUser = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser());
+ // TODO(b/179163496): should return true for profile users of the current user as well
+ return currentUser == callingUserId;
+ }
+
+ @Override
public String getUserName() {
if (!hasManageUsersOrPermission(android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) {
throw new SecurityException("You need MANAGE_USERS or GET_ACCOUNTS_PRIVILEGED "
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index 6d9cb75..2a95416 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -47,7 +47,8 @@
private static final long DELETE_AGE_MILLIS = 48 * MILLISECONDS_PER_HOUR;
private final ReentrantLock mLock = new ReentrantLock();
- private File mDataStorageDir;
+ private final File mDataStorageDir;
+ private final String mDataStorageFilename;
private final FileRotator mFileRotator;
private static class DataElement {
@@ -168,6 +169,7 @@
public PowerStatsDataStorage(Context context, File dataStoragePath,
String dataStorageFilename) {
mDataStorageDir = dataStoragePath;
+ mDataStorageFilename = dataStorageFilename;
if (!mDataStorageDir.exists() && !mDataStorageDir.mkdirs()) {
Slog.wtf(TAG, "mDataStorageDir does not exist: " + mDataStorageDir.getPath());
@@ -177,33 +179,35 @@
// filename, so any files that don't match the current version number can be deleted.
File[] files = mDataStorageDir.listFiles();
for (int i = 0; i < files.length; i++) {
- // Meter and model files are stored in the same directory.
+ // Meter, model, and residency files are stored in the same directory.
//
// The format of filenames on disk is:
// log.powerstats.meter.version.timestamp
// log.powerstats.model.version.timestamp
+ // log.powerstats.residency.version.timestamp
//
// The format of dataStorageFilenames is:
// log.powerstats.meter.version
// log.powerstats.model.version
+ // log.powerstats.residency.version
//
- // A PowerStatsDataStorage object is created for meter and model data. Strip off
- // the version and check that the current file we're checking starts with the stem
- // (log.powerstats.meter or log.powerstats.model). If the stem matches and the
- // version number is different, delete the old file.
- int versionDot = dataStorageFilename.lastIndexOf('.');
- String beforeVersionDot = dataStorageFilename.substring(0, versionDot);
+ // A PowerStatsDataStorage object is created for meter, model, and residency data.
+ // Strip off the version and check that the current file we're checking starts with
+ // the stem (log.powerstats.meter, log.powerstats.model, log.powerstats.residency).
+ // If the stem matches and the version number is different, delete the old file.
+ int versionDot = mDataStorageFilename.lastIndexOf('.');
+ String beforeVersionDot = mDataStorageFilename.substring(0, versionDot);
// Check that the stems match.
if (files[i].getName().startsWith(beforeVersionDot)) {
// Check that the version number matches. If not, delete the old file.
- if (!files[i].getName().startsWith(dataStorageFilename)) {
+ if (!files[i].getName().startsWith(mDataStorageFilename)) {
files[i].delete();
}
}
}
mFileRotator = new FileRotator(mDataStorageDir,
- dataStorageFilename,
+ mDataStorageFilename,
ROTATE_AGE_MILLIS,
DELETE_AGE_MILLIS);
}
@@ -242,4 +246,19 @@
public void read(DataElementReadCallback callback) throws IOException {
mFileRotator.readMatching(new DataReader(callback), Long.MIN_VALUE, Long.MAX_VALUE);
}
+
+ /**
+ * Deletes all stored log data.
+ */
+ public void deleteLogs() {
+ File[] files = mDataStorageDir.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ int versionDot = mDataStorageFilename.lastIndexOf('.');
+ String beforeVersionDot = mDataStorageFilename.substring(0, versionDot);
+ // Check that the stems before the version match.
+ if (files[i].getName().startsWith(beforeVersionDot)) {
+ files[i].delete();
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index 37fc5a0..c4f29ea 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.util.AtomicFile;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
@@ -41,14 +42,17 @@
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.Arrays;
/**
- * PowerStatsLogger is responsible for logging model and meter energy data to on-device storage.
- * Messages are sent to its message handler to request that energy data be logged, at which time it
- * queries the PowerStats HAL and logs the data to on-device storage. The on-device storage is
- * dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or writeResidencyDataToFile
- * with a file descriptor that points to the output file.
+ * PowerStatsLogger is responsible for logging model, meter, and residency data to on-device
+ * storage. Messages are sent to its message handler to request that energy data be logged, at
+ * which time it queries the PowerStats HAL and logs the data to on-device storage. The on-device
+ * storage is dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or
+ * writeResidencyDataToFile with a file descriptor that points to the output file.
*/
public final class PowerStatsLogger extends Handler {
private static final String TAG = PowerStatsLogger.class.getSimpleName();
@@ -61,6 +65,10 @@
private final PowerStatsDataStorage mPowerStatsModelStorage;
private final PowerStatsDataStorage mPowerStatsResidencyStorage;
private final IPowerStatsHALWrapper mPowerStatsHALWrapper;
+ private File mDataStoragePath;
+ private boolean mDeleteMeterDataOnBoot;
+ private boolean mDeleteModelDataOnBoot;
+ private boolean mDeleteResidencyDataOnBoot;
@Override
public void handleMessage(Message msg) {
@@ -230,16 +238,99 @@
pos.flush();
}
- public PowerStatsLogger(Context context, File dataStoragePath, String meterFilename,
- String modelFilename, String residencyFilename,
+ private boolean dataChanged(String cachedFilename, byte[] dataCurrent) {
+ boolean dataChanged = false;
+
+ if (mDataStoragePath.exists() || mDataStoragePath.mkdirs()) {
+ final File cachedFile = new File(mDataStoragePath, cachedFilename);
+
+ if (cachedFile.exists()) {
+ // Get the byte array for the cached data.
+ final byte[] dataCached = new byte[(int) cachedFile.length()];
+
+ // Get the cached data from file.
+ try {
+ final FileInputStream fis = new FileInputStream(cachedFile.getPath());
+ fis.read(dataCached);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read cached data from file");
+ }
+
+ // If the cached and current data are different, delete the data store.
+ dataChanged = !Arrays.equals(dataCached, dataCurrent);
+ } else {
+ // Either the cached file was somehow deleted, or this is the first
+ // boot of the device and we're creating the file for the first time.
+ // In either case, delete the log files.
+ dataChanged = true;
+ }
+ }
+
+ return dataChanged;
+ }
+
+ private void updateCacheFile(String cacheFilename, byte[] data) {
+ try {
+ final AtomicFile atomicCachedFile =
+ new AtomicFile(new File(mDataStoragePath, cacheFilename));
+ final FileOutputStream fos = atomicCachedFile.startWrite();
+ fos.write(data);
+ atomicCachedFile.finishWrite(fos);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write current data to cached file");
+ }
+ }
+
+ public boolean getDeleteMeterDataOnBoot() {
+ return mDeleteMeterDataOnBoot;
+ }
+
+ public boolean getDeleteModelDataOnBoot() {
+ return mDeleteModelDataOnBoot;
+ }
+
+ public boolean getDeleteResidencyDataOnBoot() {
+ return mDeleteResidencyDataOnBoot;
+ }
+
+ public PowerStatsLogger(Context context, File dataStoragePath,
+ String meterFilename, String meterCacheFilename,
+ String modelFilename, String modelCacheFilename,
+ String residencyFilename, String residencyCacheFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
super(Looper.getMainLooper());
mPowerStatsHALWrapper = powerStatsHALWrapper;
- mPowerStatsMeterStorage = new PowerStatsDataStorage(context, dataStoragePath,
+ mDataStoragePath = dataStoragePath;
+
+ mPowerStatsMeterStorage = new PowerStatsDataStorage(context, mDataStoragePath,
meterFilename);
- mPowerStatsModelStorage = new PowerStatsDataStorage(context, dataStoragePath,
+ mPowerStatsModelStorage = new PowerStatsDataStorage(context, mDataStoragePath,
modelFilename);
- mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, dataStoragePath,
+ mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, mDataStoragePath,
residencyFilename);
+
+ final Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo();
+ final byte[] channelBytes = ChannelUtils.getProtoBytes(channels);
+ mDeleteMeterDataOnBoot = dataChanged(meterCacheFilename, channelBytes);
+ if (mDeleteMeterDataOnBoot) {
+ mPowerStatsMeterStorage.deleteLogs();
+ updateCacheFile(meterCacheFilename, channelBytes);
+ }
+
+ final EnergyConsumer[] energyConsumers = mPowerStatsHALWrapper.getEnergyConsumerInfo();
+ final byte[] energyConsumerBytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+ mDeleteModelDataOnBoot = dataChanged(modelCacheFilename, energyConsumerBytes);
+ if (mDeleteModelDataOnBoot) {
+ mPowerStatsModelStorage.deleteLogs();
+ updateCacheFile(modelCacheFilename, energyConsumerBytes);
+ }
+
+ final PowerEntity[] powerEntities = mPowerStatsHALWrapper.getPowerEntityInfo();
+ final byte[] powerEntityBytes = PowerEntityUtils.getProtoBytes(powerEntities);
+ mDeleteResidencyDataOnBoot = dataChanged(residencyCacheFilename, powerEntityBytes);
+ if (mDeleteResidencyDataOnBoot) {
+ mPowerStatsResidencyStorage.deleteLogs();
+ updateCacheFile(residencyCacheFilename, powerEntityBytes);
+ }
}
}
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index b7285d5..bb52c1d 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -61,8 +61,12 @@
private static final String MODEL_FILENAME = "log.powerstats.model." + DATA_STORAGE_VERSION;
private static final String RESIDENCY_FILENAME =
"log.powerstats.residency." + DATA_STORAGE_VERSION;
+ private static final String METER_CACHE_FILENAME = "meterCache";
+ private static final String MODEL_CACHE_FILENAME = "modelCache";
+ private static final String RESIDENCY_CACHE_FILENAME = "residencyCache";
private final Injector mInjector;
+ private File mDataStoragePath;
private Context mContext;
@Nullable
@@ -98,6 +102,18 @@
return RESIDENCY_FILENAME;
}
+ String createMeterCacheFilename() {
+ return METER_CACHE_FILENAME;
+ }
+
+ String createModelCacheFilename() {
+ return MODEL_CACHE_FILENAME;
+ }
+
+ String createResidencyCacheFilename() {
+ return RESIDENCY_CACHE_FILENAME;
+ }
+
IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() {
return PowerStatsHALWrapper.getPowerStatsHalImpl();
}
@@ -112,10 +128,15 @@
}
PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
- String meterFilename, String modelFilename, String residencyFilename,
+ String meterFilename, String meterCacheFilename,
+ String modelFilename, String modelCacheFilename,
+ String residencyFilename, String residencyCacheFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
- return new PowerStatsLogger(context, dataStoragePath, meterFilename,
- modelFilename, residencyFilename, powerStatsHALWrapper);
+ return new PowerStatsLogger(context, dataStoragePath,
+ meterFilename, meterCacheFilename,
+ modelFilename, modelCacheFilename,
+ residencyFilename, residencyCacheFilename,
+ powerStatsHALWrapper);
}
BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) {
@@ -187,14 +208,31 @@
mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
}
+ @VisibleForTesting
+ public boolean getDeleteMeterDataOnBoot() {
+ return mPowerStatsLogger.getDeleteMeterDataOnBoot();
+ }
+
+ @VisibleForTesting
+ public boolean getDeleteModelDataOnBoot() {
+ return mPowerStatsLogger.getDeleteModelDataOnBoot();
+ }
+
+ @VisibleForTesting
+ public boolean getDeleteResidencyDataOnBoot() {
+ return mPowerStatsLogger.getDeleteResidencyDataOnBoot();
+ }
+
private void onBootCompleted() {
if (getPowerStatsHal().isInitialized()) {
if (DEBUG) Slog.d(TAG, "Starting PowerStatsService loggers");
+ mDataStoragePath = mInjector.createDataStoragePath();
// Only start logger and triggers if initialization is successful.
- mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext,
- mInjector.createDataStoragePath(), mInjector.createMeterFilename(),
- mInjector.createModelFilename(), mInjector.createResidencyFilename(),
+ mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mDataStoragePath,
+ mInjector.createMeterFilename(), mInjector.createMeterCacheFilename(),
+ mInjector.createModelFilename(), mInjector.createModelCacheFilename(),
+ mInjector.createResidencyFilename(), mInjector.createResidencyCacheFilename(),
getPowerStatsHal());
mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger);
mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger);
diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
index bd003d3..11b22a5 100644
--- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
+++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
@@ -49,6 +49,12 @@
private static final String TAG = ProtoStreamUtils.class.getSimpleName();
static class PowerEntityUtils {
+ public static byte[] getProtoBytes(PowerEntity[] powerEntity) {
+ ProtoOutputStream pos = new ProtoOutputStream();
+ packProtoMessage(powerEntity, pos);
+ return pos.getBytes();
+ }
+
public static void packProtoMessage(PowerEntity[] powerEntity,
ProtoOutputStream pos) {
if (powerEntity == null) return;
@@ -260,6 +266,12 @@
}
static class ChannelUtils {
+ public static byte[] getProtoBytes(Channel[] channel) {
+ ProtoOutputStream pos = new ProtoOutputStream();
+ packProtoMessage(channel, pos);
+ return pos.getBytes();
+ }
+
public static void packProtoMessage(Channel[] channel, ProtoOutputStream pos) {
if (channel == null) return;
@@ -396,6 +408,12 @@
}
static class EnergyConsumerUtils {
+ public static byte[] getProtoBytes(EnergyConsumer[] energyConsumer) {
+ ProtoOutputStream pos = new ProtoOutputStream();
+ packProtoMessage(energyConsumer, pos);
+ return pos.getBytes();
+ }
+
public static void packProtoMessage(EnergyConsumer[] energyConsumer,
ProtoOutputStream pos) {
if (energyConsumer == null) return;
@@ -410,6 +428,72 @@
}
}
+ public static EnergyConsumer[] unpackProtoMessage(byte[] data) throws IOException {
+ final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data));
+ List<EnergyConsumer> energyConsumerList = new ArrayList<EnergyConsumer>();
+
+ while (true) {
+ try {
+ int nextField = pis.nextField();
+ EnergyConsumer energyConsumer = new EnergyConsumer();
+
+ if (nextField == (int) PowerStatsServiceModelProto.ENERGY_CONSUMER) {
+ long token = pis.start(PowerStatsServiceModelProto.ENERGY_CONSUMER);
+ energyConsumerList.add(unpackEnergyConsumerProto(pis));
+ pis.end(token);
+ } else if (nextField == ProtoInputStream.NO_MORE_FIELDS) {
+ return energyConsumerList.toArray(
+ new EnergyConsumer[energyConsumerList.size()]);
+ } else {
+ Slog.e(TAG, "Unhandled field in proto: "
+ + ProtoUtils.currentFieldToString(pis));
+ }
+ } catch (WireTypeMismatchException wtme) {
+ Slog.e(TAG, "Wire Type mismatch in proto: "
+ + ProtoUtils.currentFieldToString(pis));
+ }
+ }
+ }
+
+ private static EnergyConsumer unpackEnergyConsumerProto(ProtoInputStream pis)
+ throws IOException {
+ final EnergyConsumer energyConsumer = new EnergyConsumer();
+
+ while (true) {
+ try {
+ switch (pis.nextField()) {
+ case (int) EnergyConsumerProto.ID:
+ energyConsumer.id = pis.readInt(EnergyConsumerProto.ID);
+ break;
+
+ case (int) EnergyConsumerProto.ORDINAL:
+ energyConsumer.ordinal = pis.readInt(EnergyConsumerProto.ORDINAL);
+ break;
+
+ case (int) EnergyConsumerProto.TYPE:
+ energyConsumer.type = (byte) pis.readInt(EnergyConsumerProto.TYPE);
+ break;
+
+ case (int) EnergyConsumerProto.NAME:
+ energyConsumer.name = pis.readString(EnergyConsumerProto.NAME);
+ break;
+
+ case ProtoInputStream.NO_MORE_FIELDS:
+ return energyConsumer;
+
+ default:
+ Slog.e(TAG, "Unhandled field in EnergyConsumerProto: "
+ + ProtoUtils.currentFieldToString(pis));
+ break;
+
+ }
+ } catch (WireTypeMismatchException wtme) {
+ Slog.e(TAG, "Wire Type mismatch in EnergyConsumerProto: "
+ + ProtoUtils.currentFieldToString(pis));
+ }
+ }
+ }
+
public static void print(EnergyConsumer[] energyConsumer) {
if (energyConsumer == null) return;
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 3726407..02a597e 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -30,7 +30,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.server.VcnManagementService.VcnSafeModeCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import java.util.Collections;
@@ -86,18 +86,18 @@
private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
/**
- * Causes this VCN to immediately enter Safemode.
+ * Causes this VCN to immediately enter safe mode.
*
- * <p>Upon entering Safemode, the VCN will unregister its RequestListener, tear down all of its
- * VcnGatewayConnections, and notify VcnManagementService that it is in Safemode.
+ * <p>Upon entering safe mode, the VCN will unregister its RequestListener, tear down all of its
+ * VcnGatewayConnections, and notify VcnManagementService that it is in safe mode.
*/
- private static final int MSG_CMD_ENTER_SAFEMODE = MSG_CMD_BASE + 1;
+ private static final int MSG_CMD_ENTER_SAFE_MODE = MSG_CMD_BASE + 1;
@NonNull private final VcnContext mVcnContext;
@NonNull private final ParcelUuid mSubscriptionGroup;
@NonNull private final Dependencies mDeps;
@NonNull private final VcnNetworkRequestListener mRequestListener;
- @NonNull private final VcnSafemodeCallback mVcnSafemodeCallback;
+ @NonNull private final VcnSafeModeCallback mVcnSafeModeCallback;
@NonNull
private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
@@ -125,13 +125,13 @@
@NonNull ParcelUuid subscriptionGroup,
@NonNull VcnConfig config,
@NonNull TelephonySubscriptionSnapshot snapshot,
- @NonNull VcnSafemodeCallback vcnSafemodeCallback) {
+ @NonNull VcnSafeModeCallback vcnSafeModeCallback) {
this(
vcnContext,
subscriptionGroup,
config,
snapshot,
- vcnSafemodeCallback,
+ vcnSafeModeCallback,
new Dependencies());
}
@@ -141,13 +141,13 @@
@NonNull ParcelUuid subscriptionGroup,
@NonNull VcnConfig config,
@NonNull TelephonySubscriptionSnapshot snapshot,
- @NonNull VcnSafemodeCallback vcnSafemodeCallback,
+ @NonNull VcnSafeModeCallback vcnSafeModeCallback,
@NonNull Dependencies deps) {
super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
mVcnContext = vcnContext;
mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
- mVcnSafemodeCallback =
- Objects.requireNonNull(vcnSafemodeCallback, "Missing vcnSafemodeCallback");
+ mVcnSafeModeCallback =
+ Objects.requireNonNull(vcnSafeModeCallback, "Missing vcnSafeModeCallback");
mDeps = Objects.requireNonNull(deps, "Missing deps");
mRequestListener = new VcnNetworkRequestListener();
@@ -216,8 +216,8 @@
case MSG_CMD_TEARDOWN:
handleTeardown();
break;
- case MSG_CMD_ENTER_SAFEMODE:
- handleEnterSafemode();
+ case MSG_CMD_ENTER_SAFE_MODE:
+ handleEnterSafeMode();
break;
default:
Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
@@ -243,10 +243,10 @@
mIsActive.set(false);
}
- private void handleEnterSafemode() {
+ private void handleEnterSafeMode() {
handleTeardown();
- mVcnSafemodeCallback.onEnteredSafemode();
+ mVcnSafeModeCallback.onEnteredSafeMode();
}
private void handleNetworkRequested(
@@ -335,14 +335,14 @@
/** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
@VisibleForTesting(visibility = Visibility.PACKAGE)
public interface VcnGatewayStatusCallback {
- /** Called by a VcnGatewayConnection to indicate that it has entered Safemode. */
- void onEnteredSafemode();
+ /** Called by a VcnGatewayConnection to indicate that it has entered safe mode. */
+ void onEnteredSafeMode();
}
private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
@Override
- public void onEnteredSafemode() {
- sendMessage(obtainMessage(MSG_CMD_ENTER_SAFEMODE));
+ public void onEnteredSafeMode() {
+ sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE));
}
}
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 12590eb..59cb6ac 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -418,13 +418,13 @@
private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;
/**
- * Sent when this VcnGatewayConnection has entered Safemode.
+ * Sent when this VcnGatewayConnection has entered safe mode.
*
- * <p>A VcnGatewayConnection enters Safemode when it takes over {@link
+ * <p>A VcnGatewayConnection enters safe mode when it takes over {@link
* #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}.
*
* <p>When a VcnGatewayConnection enters safe mode, it will fire {@link
- * VcnGatewayStatusCallback#onEnteredSafemode()} to notify its Vcn. The Vcn will then shut down
+ * VcnGatewayStatusCallback#onEnteredSafeMode()} to notify its Vcn. The Vcn will then shut down
* its VcnGatewayConnectin(s).
*
* <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not
@@ -432,7 +432,7 @@
*
* @param arg1 The "all" token; this signal is always honored.
*/
- private static final int EVENT_SAFEMODE_TIMEOUT_EXCEEDED = 10;
+ private static final int EVENT_SAFE_MODE_TIMEOUT_EXCEEDED = 10;
@VisibleForTesting(visibility = Visibility.PRIVATE)
@NonNull
@@ -551,7 +551,7 @@
@Nullable private WakeupMessage mTeardownTimeoutAlarm;
@Nullable private WakeupMessage mDisconnectRequestAlarm;
@Nullable private WakeupMessage mRetryTimeoutAlarm;
- @Nullable private WakeupMessage mSafemodeTimeoutAlarm;
+ @Nullable private WakeupMessage mSafeModeTimeoutAlarm;
public VcnGatewayConnection(
@NonNull VcnContext vcnContext,
@@ -638,7 +638,7 @@
cancelTeardownTimeoutAlarm();
cancelDisconnectRequestAlarm();
cancelRetryTimeoutAlarm();
- cancelSafemodeAlarm();
+ cancelSafeModeAlarm();
mUnderlyingNetworkTracker.teardown();
}
@@ -928,27 +928,27 @@
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
- void setSafemodeAlarm() {
+ void setSafeModeAlarm() {
// Only schedule a NEW alarm if none is already set.
- if (mSafemodeTimeoutAlarm != null) {
+ if (mSafeModeTimeoutAlarm != null) {
return;
}
- final Message delayedMessage = obtainMessage(EVENT_SAFEMODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
- mSafemodeTimeoutAlarm =
+ final Message delayedMessage = obtainMessage(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
+ mSafeModeTimeoutAlarm =
createScheduledAlarm(
SAFEMODE_TIMEOUT_ALARM,
delayedMessage,
TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
}
- private void cancelSafemodeAlarm() {
- if (mSafemodeTimeoutAlarm != null) {
- mSafemodeTimeoutAlarm.cancel();
- mSafemodeTimeoutAlarm = null;
+ private void cancelSafeModeAlarm() {
+ if (mSafeModeTimeoutAlarm != null) {
+ mSafeModeTimeoutAlarm.cancel();
+ mSafeModeTimeoutAlarm = null;
}
- removeEqualMessages(EVENT_SAFEMODE_TIMEOUT_EXCEEDED);
+ removeEqualMessages(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED);
}
private void sessionLost(int token, @Nullable Exception exception) {
@@ -1125,7 +1125,7 @@
Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
}
- cancelSafemodeAlarm();
+ cancelSafeModeAlarm();
}
@Override
@@ -1153,7 +1153,7 @@
@Override
protected void exitState() {
// Safe to blindly set up, as it is cancelled and cleared on entering this state
- setSafemodeAlarm();
+ setSafeModeAlarm();
}
}
@@ -1245,9 +1245,9 @@
transitionTo(mDisconnectedState);
}
break;
- case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
- mGatewayStatusCallback.onEnteredSafemode();
- mSafemodeTimeoutAlarm = null;
+ case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+ mGatewayStatusCallback.onEnteredSafeMode();
+ mSafeModeTimeoutAlarm = null;
break;
default:
logUnhandledMessage(msg);
@@ -1331,9 +1331,9 @@
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
break;
- case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
- mGatewayStatusCallback.onEnteredSafemode();
- mSafemodeTimeoutAlarm = null;
+ case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+ mGatewayStatusCallback.onEnteredSafeMode();
+ mSafeModeTimeoutAlarm = null;
break;
default:
logUnhandledMessage(msg);
@@ -1399,7 +1399,7 @@
// Validated connection, clear failed attempt counter
mFailedAttempts = 0;
- cancelSafemodeAlarm();
+ cancelSafeModeAlarm();
}
protected void applyTransform(
@@ -1517,9 +1517,9 @@
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
break;
- case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
- mGatewayStatusCallback.onEnteredSafemode();
- mSafemodeTimeoutAlarm = null;
+ case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+ mGatewayStatusCallback.onEnteredSafeMode();
+ mSafeModeTimeoutAlarm = null;
break;
default:
logUnhandledMessage(msg);
@@ -1575,7 +1575,7 @@
protected void exitState() {
// Attempt to set the safe mode alarm - this requires the Vcn Network being validated
// while in ConnectedState (which cancels the previous alarm)
- setSafemodeAlarm();
+ setSafeModeAlarm();
}
}
@@ -1623,9 +1623,9 @@
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
break;
- case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
- mGatewayStatusCallback.onEnteredSafemode();
- mSafemodeTimeoutAlarm = null;
+ case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+ mGatewayStatusCallback.onEnteredSafeMode();
+ mSafeModeTimeoutAlarm = null;
break;
default:
logUnhandledMessage(msg);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 370d921..5697564 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -31,7 +31,6 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
-import android.app.ILocalWallpaperColorConsumer;
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
import android.app.PendingIntent;
@@ -60,7 +59,6 @@
import android.graphics.BitmapRegionDecoder;
import android.graphics.Color;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Bundle;
@@ -87,10 +85,7 @@
import android.service.wallpaper.WallpaperService;
import android.system.ErrnoException;
import android.system.Os;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.EventLog;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -110,6 +105,7 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.WindowManagerInternal;
@@ -140,8 +136,6 @@
private static final String TAG = "WallpaperManagerService";
private static final boolean DEBUG = false;
private static final boolean DEBUG_LIVE = true;
- private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
- new RectF(0, 0, 1, 1);
public static class Lifecycle extends SystemService {
private IWallpaperManagerService mService;
@@ -872,12 +866,6 @@
private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
private int mCurrentUserId = UserHandle.USER_NULL;
private boolean mInAmbientMode;
- private ArrayMap<IBinder, ArraySet<RectF>> mLocalColorCallbackAreas =
- new ArrayMap<>();
- private ArrayMap<RectF, RemoteCallbackList<ILocalWallpaperColorConsumer>>
- mLocalColorAreaCallbacks = new ArrayMap<>();
- private ArrayMap<Integer, ArraySet<RectF>> mLocalColorDisplayIdAreas = new ArrayMap<>();
- private ArrayMap<IBinder, Integer> mLocalColorCallbackDisplayId = new ArrayMap<>();
static class WallpaperData {
@@ -1288,32 +1276,6 @@
}
@Override
- public void onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors,
- int displayId) {
- forEachDisplayConnector(displayConnector -> {
- if (displayConnector.mDisplayId == displayId) {
- RemoteCallbackList<ILocalWallpaperColorConsumer> callbacks;
- ArrayMap<IBinder, Integer> callbackDisplayIds;
- synchronized (mLock) {
- callbacks = mLocalColorAreaCallbacks.get(area);
- callbackDisplayIds = new ArrayMap<>(mLocalColorCallbackDisplayId);
- }
- if (callbacks == null) return;
- callbacks.broadcast(c -> {
- try {
- int targetDisplayId =
- callbackDisplayIds.get(c.asBinder());
- if (targetDisplayId == displayId) c.onColorsChanged(area, colors);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- });
- }
- });
- }
-
-
- @Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
Slog.w(TAG, "Wallpaper service gone: " + name);
@@ -1475,15 +1437,6 @@
} catch (RemoteException e) {
Slog.w(TAG, "Failed to request wallpaper colors", e);
}
-
- ArraySet<RectF> areas = mLocalColorDisplayIdAreas.get(displayId);
- if (areas != null && areas.size() != 0) {
- try {
- connector.mEngine.addLocalColorsAreas(new ArrayList<>(areas));
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to register local colors areas", e);
- }
- }
}
}
@@ -2387,115 +2340,6 @@
return true;
}
- private IWallpaperEngine getEngine(int which, int userId, int displayId) {
- WallpaperData wallpaperData = findWallpaperAtDisplay(userId, displayId);
- if (wallpaperData == null) return null;
- IWallpaperEngine engine = null;
- synchronized (mLock) {
- for (int i = 0; i < wallpaperData.connection.mDisplayConnector.size(); i++) {
- int id = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId;
- int currentWhich = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId;
- if (id != displayId && currentWhich != which) continue;
- engine = wallpaperData.connection.mDisplayConnector.get(i).mEngine;
- break;
- }
- }
- return engine;
- }
-
- @Override
- public void addOnLocalColorsChangedListener(@NonNull ILocalWallpaperColorConsumer callback,
- @NonNull List<RectF> regions, int which, int userId, int displayId)
- throws RemoteException {
- if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
- throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
- }
- IWallpaperEngine engine = getEngine(which, userId, displayId);
- if (engine == null) return;
- ArrayList<RectF> validAreas = new ArrayList<>(regions.size());
- synchronized (mLock) {
- ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback);
- if (areas == null) areas = new ArraySet<>(regions.size());
- areas.addAll(regions);
- mLocalColorCallbackAreas.put(callback.asBinder(), areas);
- }
- for (int i = 0; i < regions.size(); i++) {
- if (!LOCAL_COLOR_BOUNDS.contains(regions.get(i))) {
- continue;
- }
- RemoteCallbackList callbacks;
- synchronized (mLock) {
- callbacks = mLocalColorAreaCallbacks.get(
- regions.get(i));
- if (callbacks == null) {
- callbacks = new RemoteCallbackList();
- mLocalColorAreaCallbacks.put(regions.get(i), callbacks);
- }
- mLocalColorCallbackDisplayId.put(callback.asBinder(), displayId);
- ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
- if (displayAreas == null) {
- displayAreas = new ArraySet<>(1);
- mLocalColorDisplayIdAreas.put(displayId, displayAreas);
- }
- displayAreas.add(regions.get(i));
- }
- validAreas.add(regions.get(i));
- callbacks.register(callback);
- }
- engine.addLocalColorsAreas(validAreas);
- }
-
- @Override
- public void removeOnLocalColorsChangedListener(
- @NonNull ILocalWallpaperColorConsumer callback, int which, int userId,
- int displayId) throws RemoteException {
- if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
- throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
- }
- final UserHandle callingUser = Binder.getCallingUserHandle();
- if (callingUser.getIdentifier() != userId) {
- throw new SecurityException("calling user id does not match");
- }
- final long identity = Binder.clearCallingIdentity();
- ArrayList<RectF> removeAreas = new ArrayList<>();
- ArrayList<Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>>
- callbacksToRemove = new ArrayList<>();
- try {
- synchronized (mLock) {
- ArraySet<RectF> areas = mLocalColorCallbackAreas.remove(callback.asBinder());
- mLocalColorCallbackDisplayId.remove(callback.asBinder());
- if (areas == null) areas = new ArraySet<>();
- for (RectF area : areas) {
- RemoteCallbackList callbacks = mLocalColorAreaCallbacks.get(area);
- if (callbacks == null) continue;
- callbacksToRemove.add(new Pair<>(callbacks, callback));
- if (callbacks.getRegisteredCallbackCount() == 0) {
- mLocalColorAreaCallbacks.remove(area);
- removeAreas.add(area);
- }
- ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
- if (displayAreas != null) {
- displayAreas.remove(area);
- }
- }
- }
- for (int i = 0; i < callbacksToRemove.size(); i++) {
- Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>
- pair = callbacksToRemove.get(i);
- pair.first.unregister(pair.second);
- }
- } catch (Exception e) {
- // ignore any exception
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
-
- if (removeAreas.size() == 0) return;
- IWallpaperEngine engine = getEngine(which, userId, displayId);
- if (engine == null) return;
- engine.removeLocalColorsAreas(removeAreas);
- }
-
@Override
public WallpaperColors getWallpaperColors(int which, int userId, int displayId)
throws RemoteException {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6fbeaa4..bb5e8bf 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -847,7 +847,6 @@
mWmService.openSurfaceTransaction();
try {
applySurfaceChangesTransaction();
- mWmService.mSyncEngine.onSurfacePlacement();
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
@@ -861,6 +860,7 @@
// Send any pending task-info changes that were queued-up during a layout deferment
mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+ mWmService.mSyncEngine.onSurfacePlacement();
mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
checkAppTransitionReady(surfacePlacer);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9772c2e..9250894 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -247,6 +247,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
+import android.permission.AdminPermissionControlParams;
import android.permission.IPermissionManager;
import android.permission.PermissionControllerManager;
import android.provider.CalendarContract;
@@ -422,10 +423,12 @@
DELEGATION_CERT_SELECTION,
};
- // Subset of delegations that can only be delegated by Device Owner.
- private static final List<String> DEVICE_OWNER_DELEGATIONS = Arrays.asList(new String[] {
- DELEGATION_NETWORK_LOGGING,
- });
+ // Subset of delegations that can only be delegated by Device Owner or Profile Owner of a
+ // managed profile.
+ private static final List<String> DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS =
+ Arrays.asList(new String[]{
+ DELEGATION_NETWORK_LOGGING,
+ });
// Subset of delegations that only one single package within a given user can hold
private static final List<String> EXCLUSIVE_DELEGATIONS = Arrays.asList(new String[] {
@@ -531,8 +534,10 @@
/**
* Strings logged with {@link
- * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}
- * and {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB}.
+ * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB},
+ * {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB},
+ * {@link DevicePolicyEnums#SET_NETWORK_LOGGING_ENABLED} and
+ * {@link DevicePolicyEnums#RETRIEVE_NETWORK_LOGS}.
*/
private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
@@ -5884,10 +5889,10 @@
}
// Retrieve the user ID of the calling process.
final int userId = caller.getUserId();
- final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS);
// Ensure calling process is device/profile owner.
- if (hasDoDelegation) {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ if (!Collections.disjoint(scopes, DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS)) {
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())));
} else {
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
}
@@ -6448,7 +6453,7 @@
private void forceWipeUser(int userId, String wipeReasonForUser, boolean wipeSilently) {
boolean success = false;
try {
- if (getCurrentForegroundUser() == userId) {
+ if (getCurrentForegroundUserId() == userId) {
mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM);
}
@@ -7889,6 +7894,14 @@
updateDeviceOwnerLocked();
setDeviceOwnershipSystemPropertyLocked();
+ //TODO(b/180371154): when provisionFullyManagedDevice is used in tests, remove this
+ // hard-coded default value setting.
+ if (isAdb(caller)) {
+ activeAdmin.mAdminCanGrantSensorsPermissions = true;
+ mPolicyCache.setAdminCanGrantSensorsPermissions(userId, true);
+ saveSettingsLocked(userId);
+ }
+
mInjector.binderWithCleanCallingIdentity(() -> {
// Restrict adding a managed profile when a device owner is set on the device.
// That is to prevent the co-existence of a managed profile and a device owner
@@ -7908,7 +7921,7 @@
Slog.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId);
if (mInjector.userManagerIsHeadlessSystemUserMode()) {
- int currentForegroundUser = getCurrentForegroundUser();
+ int currentForegroundUser = getCurrentForegroundUserId();
Slog.i(LOG_TAG, "setDeviceOwner(): setting " + admin
+ " as profile owner on user " + currentForegroundUser);
// Sets profile owner on current foreground user since
@@ -9039,7 +9052,7 @@
return UserHandle.isSameApp(caller.getUid(), Process.SHELL_UID);
}
- private @UserIdInt int getCurrentForegroundUser() {
+ private @UserIdInt int getCurrentForegroundUserId() {
try {
return mInjector.getIActivityManager().getCurrentUser().id;
} catch (RemoteException e) {
@@ -9048,6 +9061,25 @@
return UserHandle.USER_NULL;
}
+ @Override
+ public List<UserHandle> listForegroundAffiliatedUsers() {
+ checkIsDeviceOwner(getCallerIdentity());
+
+ int userId = mInjector.binderWithCleanCallingIdentity(() -> getCurrentForegroundUserId());
+
+ boolean isAffiliated;
+ synchronized (getLockObject()) {
+ isAffiliated = isUserAffiliatedWithDeviceLocked(userId);
+ }
+
+ if (!isAffiliated) return Collections.emptyList();
+
+ List<UserHandle> users = new ArrayList<>(1);
+ users.add(UserHandle.of(userId));
+
+ return users;
+ }
+
protected int getProfileParentId(int userHandle) {
return mInjector.binderWithCleanCallingIdentity(() -> {
UserInfo parentUser = mUserManager.getProfileParent(userHandle);
@@ -12641,9 +12673,12 @@
if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
|| grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
|| grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+ AdminPermissionControlParams permissionParams =
+ new AdminPermissionControlParams(packageName, permission, grantState,
+ canAdminGrantSensorsPermissionsForUser(caller.getUserId()));
mInjector.getPermissionControllerManager(caller.getUserHandle())
.setRuntimePermissionGrantStateByDeviceAdmin(caller.getPackageName(),
- packageName, permission, grantState, mContext.getMainExecutor(),
+ permissionParams, mContext.getMainExecutor(),
(permissionWasSet) -> {
if (isPostQAdmin && !permissionWasSet) {
callback.sendResult(null);
@@ -12859,7 +12894,7 @@
return CODE_NONSYSTEM_USER_EXISTS;
}
- int currentForegroundUser = getCurrentForegroundUser();
+ int currentForegroundUser = getCurrentForegroundUserId();
if (callingUserId != currentForegroundUser
&& mInjector.userManagerIsHeadlessSystemUserMode()
&& currentForegroundUser == UserHandle.USER_SYSTEM) {
@@ -12955,6 +12990,11 @@
return CODE_OK;
}
+ private void checkIsDeviceOwner(CallerIdentity caller) {
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller), caller.getUid()
+ + " is not device owner");
+ }
+
private ComponentName getOwnerComponent(String packageName, int userId) {
if (isDeviceOwnerPackage(packageName, userId)) {
return mOwners.getDeviceOwnerComponent();
@@ -14151,9 +14191,10 @@
return;
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
+ final boolean isManagedProfileOwner = isProfileOwner(caller)
+ && isManagedProfile(caller.getUserId());
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isDeviceOwner(caller)
- || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
+ && (isDeviceOwner(caller) || isManagedProfileOwner))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
synchronized (getLockObject()) {
@@ -14175,6 +14216,8 @@
.setAdmin(caller.getPackageName())
.setBoolean(/* isDelegate */ admin == null)
.setInt(enabled ? 1 : 0)
+ .setStrings(isManagedProfileOwner
+ ? LOG_TAG_PROFILE_OWNER : LOG_TAG_DEVICE_OWNER)
.write();
}
}
@@ -14331,9 +14374,10 @@
return null;
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
+ final boolean isManagedProfileOwner = isProfileOwner(caller)
+ && isManagedProfile(caller.getUserId());
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isDeviceOwner(caller)
- || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
+ && (isDeviceOwner(caller) || isManagedProfileOwner))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
if (mOwners.hasDeviceOwner()) {
checkAllUsersAreAffiliatedWithDevice();
@@ -14347,6 +14391,8 @@
.createEvent(DevicePolicyEnums.RETRIEVE_NETWORK_LOGS)
.setAdmin(caller.getPackageName())
.setBoolean(/* isDelegate */ admin == null)
+ .setStrings(isManagedProfileOwner
+ ? LOG_TAG_PROFILE_OWNER : LOG_TAG_DEVICE_OWNER)
.write();
final long currentTime = System.currentTimeMillis();
@@ -15445,7 +15491,7 @@
private boolean isLockTaskFeatureEnabled(int lockTaskFeature) throws RemoteException {
//TODO(b/175285301): Explicitly get the user's identity to check.
int lockTaskFeatures =
- getUserData(getCurrentForegroundUser()).mLockTaskFeatures;
+ getUserData(getCurrentForegroundUserId()).mLockTaskFeatures;
return (lockTaskFeatures & lockTaskFeature) == lockTaskFeature;
}
@@ -16701,6 +16747,8 @@
@Override
public boolean canUsbDataSignalingBeDisabled() {
return mInjector.binderWithCleanCallingIdentity(() ->
- mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3);
+ mInjector.getUsbManager() != null
+ && mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3
+ );
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2b09d12..dd2dd81 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -326,6 +326,8 @@
"com.android.server.musicrecognition.MusicRecognitionManagerService";
private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
"com.android.server.systemcaptions.SystemCaptionsManagerService";
+ private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS =
+ "com.android.server.texttospeech.TextToSpeechManagerService";
private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
"com.android.server.timezone.RulesManagerService$Lifecycle";
private static final String IOT_SERVICE_CLASS =
@@ -1713,6 +1715,7 @@
startAttentionService(context, t);
startRotationResolverService(context, t);
startSystemCaptionsManagerService(context, t);
+ startTextToSpeechManagerService(context, t);
// System Speech Recognition Service
if (deviceHasConfigString(context,
@@ -2918,6 +2921,13 @@
t.traceEnd();
}
+ private void startTextToSpeechManagerService(@NonNull Context context,
+ @NonNull TimingsTraceAndSlog t) {
+ t.traceBegin("StartTextToSpeechManagerService");
+ mSystemServiceManager.startService(TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+ }
+
private void startContentCaptureService(@NonNull Context context,
@NonNull TimingsTraceAndSlog t) {
// First check if it was explicitly enabled by DeviceConfig
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 5453de1..9666337 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -16,6 +16,7 @@
package com.android.server.people;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -29,6 +30,7 @@
import android.app.prediction.AppTargetEvent;
import android.app.prediction.IPredictionCallback;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -184,6 +186,20 @@
}
@Override
+ public boolean isConversation(String packageName, int userId, String shortcutId) {
+ enforceHasReadPeopleDataPermission();
+ handleIncomingUser(userId);
+ return mDataManager.isConversation(packageName, userId, shortcutId);
+ }
+
+ private void enforceHasReadPeopleDataPermission() throws SecurityException {
+ if (getContext().checkCallingPermission(Manifest.permission.READ_PEOPLE_DATA)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Caller doesn't have READ_PEOPLE_DATA permission.");
+ }
+ }
+
+ @Override
public long getLastInteraction(String packageName, int userId, String shortcutId) {
enforceSystemRootOrSystemUI(getContext(), "get last interaction");
return mDataManager.getLastInteraction(packageName, userId, shortcutId);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 444f9c6..1048dfd 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -58,6 +58,7 @@
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
+import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -354,6 +355,14 @@
});
}
+ /** Returns whether {@code shortcutId} is backed by Conversation. */
+ public boolean isConversation(String packageName, int userId, String shortcutId) {
+ ConversationChannel channel = getConversation(packageName, userId, shortcutId);
+ return channel != null
+ && channel.getShortcutInfo() != null
+ && !TextUtils.isEmpty(channel.getShortcutInfo().getLabel());
+ }
+
/**
* Returns the last notification interaction with the specified conversation. If the
* conversation can't be found or no interactions have been recorded, returns 0L.
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/values.xml
similarity index 82%
rename from services/tests/servicestests/res/values/strings.xml
rename to services/tests/servicestests/res/values/values.xml
index 1f07ad5..0404ebc 100644
--- a/services/tests/servicestests/res/values/strings.xml
+++ b/services/tests/servicestests/res/values/values.xml
@@ -36,4 +36,12 @@
<string name="module_2_name" translatable="false">module_2_name</string>
<string name="widget_description">widget description string</string>
+ <dimen name="widget_min_width">80dp</dimen>
+ <dimen name="widget_min_height">40dp</dimen>
+ <dimen name="widget_min_resize_width">40dp</dimen>
+ <dimen name="widget_min_resize_height">20dp</dimen>
+ <dimen name="widget_max_resize_width">160dp</dimen>
+ <dimen name="widget_max_resize_height">80dp</dimen>
+ <integer name="widget_target_cell_width">2</integer>
+ <integer name="widget_target_cell_height">1</integer>
</resources>
diff --git a/services/tests/servicestests/res/xml/dummy_appwidget_info.xml b/services/tests/servicestests/res/xml/dummy_appwidget_info.xml
index 72f025d..de06191 100644
--- a/services/tests/servicestests/res/xml/dummy_appwidget_info.xml
+++ b/services/tests/servicestests/res/xml/dummy_appwidget_info.xml
@@ -16,12 +16,18 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
- android:minWidth="40dp"
- android:minHeight="40dp"
+ android:minWidth="@dimen/widget_min_width"
+ android:minHeight="@dimen/widget_min_height"
+ android:minResizeWidth="@dimen/widget_min_resize_width"
+ android:minResizeHeight="@dimen/widget_min_resize_height"
+ android:maxResizeWidth="@dimen/widget_max_resize_width"
+ android:maxResizeHeight="@dimen/widget_max_resize_height"
+ android:targetCellWidth="@integer/widget_target_cell_width"
+ android:targetCellHeight="@integer/widget_target_cell_height"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/icon1"
android:previewLayout="@layout/widget_preview"
android:resizeMode="horizontal|vertical"
android:description="@string/widget_description"
android:widgetCategory="home_screen">
-</appwidget-provider>
\ No newline at end of file
+</appwidget-provider>
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index 738f008..6d4189e 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -90,7 +90,7 @@
writeGameServiceXml();
}
- private void verifyGameServiceSettingsData(Settings settings) {
+ private void verifyGameServiceSettingsData(GameManagerSettings settings) {
assertThat(settings.getGameModeLocked(PACKAGE_NAME_1), is(1));
assertThat(settings.getGameModeLocked(PACKAGE_NAME_2), is(2));
assertThat(settings.getGameModeLocked(PACKAGE_NAME_3), is(3));
@@ -107,7 +107,7 @@
/* write out files and read */
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
- Settings settings = new Settings(context.getFilesDir());
+ GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
assertThat(settings.readPersistentDataLocked(), is(true));
verifyGameServiceSettingsData(settings);
}
@@ -118,7 +118,7 @@
// write out files and read
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
- Settings settings = new Settings(context.getFilesDir());
+ GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
assertThat(settings.readPersistentDataLocked(), is(true));
// write out, read back in and verify the same
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index 78eb2df..ff8fedc 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -103,6 +103,26 @@
assertEquals(info.loadDescription(mTestContext), "widget description string");
}
+ public void testParseSizeConfiguration() {
+ AppWidgetProviderInfo info =
+ mManager.getInstalledProvidersForPackage(mPkgName, null).get(0);
+
+ assertThat(info.minWidth).isEqualTo(getDimensionResource(R.dimen.widget_min_width));
+ assertThat(info.minHeight).isEqualTo(getDimensionResource(R.dimen.widget_min_height));
+ assertThat(info.minResizeWidth)
+ .isEqualTo(getDimensionResource(R.dimen.widget_min_resize_width));
+ assertThat(info.minResizeHeight)
+ .isEqualTo(getDimensionResource(R.dimen.widget_min_resize_height));
+ assertThat(info.maxResizeWidth)
+ .isEqualTo(getDimensionResource(R.dimen.widget_max_resize_width));
+ assertThat(info.maxResizeHeight)
+ .isEqualTo(getDimensionResource(R.dimen.widget_max_resize_height));
+ assertThat(info.targetCellWidth)
+ .isEqualTo(getIntegerResource(R.integer.widget_target_cell_width));
+ assertThat(info.targetCellHeight)
+ .isEqualTo(getIntegerResource(R.integer.widget_target_cell_height));
+ }
+
public void testRequestPinAppWidget_otherProvider() {
ComponentName otherProvider = null;
for (AppWidgetProviderInfo provider : mManager.getInstalledProviders()) {
@@ -325,6 +345,14 @@
latch.await();
}
+ private int getDimensionResource(int resId) {
+ return mTestContext.getResources().getDimensionPixelSize(resId);
+ }
+
+ private int getIntegerResource(int resId) {
+ return mTestContext.getResources().getInteger(resId);
+ }
+
private class TestContext extends ContextWrapper {
public TestContext() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index bb57a69..fcbd897 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -15,12 +15,9 @@
*/
package com.android.server.hdmi;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPortInfo;
-import android.hardware.tv.cec.V1_0.CecMessage;
-import android.hardware.tv.cec.V1_0.HotplugEvent;
import android.hardware.tv.cec.V1_0.SendMessageResult;
-import android.os.RemoteException;
-import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.hdmi.HdmiCecController.NativeWrapper;
@@ -99,7 +96,7 @@
@Override
public int nativeGetVersion() {
- return 0;
+ return HdmiControlManager.HDMI_CEC_VERSION_2_0;
}
@Override
@@ -139,20 +136,15 @@
if (mCallback == null) {
return;
}
- CecMessage message = new CecMessage();
- message.initiator = hdmiCecMessage.getSource();
- message.destination = hdmiCecMessage.getDestination();
- ArrayList<Byte> body = new ArrayList<>();
- body.add((byte) hdmiCecMessage.getOpcode());
+ int source = hdmiCecMessage.getSource();
+ int destination = hdmiCecMessage.getDestination();
+ byte[] body = new byte[hdmiCecMessage.getParams().length + 1];
+ int i = 0;
+ body[i++] = (byte) hdmiCecMessage.getOpcode();
for (byte param : hdmiCecMessage.getParams()) {
- body.add(param);
+ body[i++] = param;
}
- message.body = body;
- try {
- mCallback.onCecMessage(message);
- } catch (RemoteException e) {
- Log.e(TAG, "Error sending CEC message", e);
- }
+ mCallback.onCecMessage(source, destination, body);
}
public void onHotplugEvent(int port, boolean connected) {
@@ -160,15 +152,7 @@
return;
}
- HotplugEvent hotplugEvent = new HotplugEvent();
- hotplugEvent.portId = port;
- hotplugEvent.connected = connected;
-
- try {
- mCallback.onHotplugEvent(hotplugEvent);
- } catch (RemoteException e) {
- Log.e(TAG, "Error sending hotplug event", e);
- }
+ mCallback.onHotplugEvent(port, connected);
}
public List<HdmiCecMessage> getResultMessages() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 5342486..b11ac24 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -24,6 +24,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
@@ -34,6 +36,7 @@
import android.os.IThermalService;
import android.os.Looper;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.sysprop.HdmiProperties;
@@ -1552,6 +1555,31 @@
}
@Test
+ public void toggleAndFollowTvPower_isInteractive() throws RemoteException {
+ when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+ mActiveMediaSessionsPaused = false;
+ mWokenUp = false;
+
+ mHdmiControlService.toggleAndFollowTvPower();
+
+ assertThat(mActiveMediaSessionsPaused).isTrue();
+ assertThat(mWokenUp).isFalse();
+ }
+
+ @Test
+ public void toggleAndFollowTvPower_isNotInteractive() throws RemoteException {
+ when(mIPowerManagerMock.isInteractive()).thenReturn(false);
+ mActiveMediaSessionsPaused = false;
+ mWokenUp = false;
+
+ mHdmiControlService.toggleAndFollowTvPower();
+
+ assertThat(mActiveMediaSessionsPaused).isFalse();
+ assertThat(mWokenUp).isTrue();
+ }
+
+
+ @Test
public void shouldHandleTvPowerKey_CecDisabled() {
mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 6e57896..50d9f61 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -601,6 +601,22 @@
}
@Test
+ public void testIsConversation() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+ assertThat(mDataManager.isConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID)).isFalse();
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+ assertThat(mDataManager.isConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID)).isTrue();
+ assertThat(mDataManager.isConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID + "1")).isFalse();
+ }
+
+ @Test
public void testNotificationChannelCreated() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
mDataManager.onUserUnlocked(USER_ID_SECONDARY);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 23fcf70..4d0beef 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -103,7 +103,6 @@
import android.util.Xml;
import com.android.frameworks.servicestests.R;
-import com.android.internal.util.FastXmlSerializer;
import com.android.server.pm.ShortcutService.ConfigConstants;
import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
import com.android.server.pm.ShortcutUser.PackageWithUser;
@@ -111,7 +110,6 @@
import org.mockito.ArgumentCaptor;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -141,6 +139,7 @@
private static final int CACHE_OWNER_0 = LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
private static final int CACHE_OWNER_1 = LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
+ private static final int CACHE_OWNER_2 = LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
@Override
protected void tearDown() throws Exception {
@@ -1531,7 +1530,8 @@
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
- makeLongLivedShortcut("s4"))));
+ makeLongLivedShortcut("s4"), makeLongLivedShortcut("s5"),
+ makeLongLivedShortcut("s6"))));
});
// Pin s2
@@ -1545,28 +1545,30 @@
mInjectCheckAccessShortcutsPermission = true;
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"),
HANDLE_USER_0, CACHE_OWNER_0);
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4", "s5"),
HANDLE_USER_0, CACHE_OWNER_1);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s5", "s6"),
+ HANDLE_USER_0, CACHE_OWNER_2);
});
setCaller(CALLING_PACKAGE_1);
// Get dynamic shortcuts
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
- "s1", "s2", "s3", "s4");
+ "s1", "s2", "s3", "s4", "s5", "s6");
// Get pinned shortcuts
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED),
"s2");
// Get cached shortcuts
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s2", "s4");
+ "s2", "s4", "s5", "s6");
// Remove a dynamic cached shortcut
- mManager.removeDynamicShortcuts(list("s4"));
+ mManager.removeDynamicShortcuts(list("s4", "s5"));
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
- "s1", "s2", "s3");
+ "s1", "s2", "s3", "s6");
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s2", "s4");
+ "s2", "s4", "s5", "s6");
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
@@ -1574,15 +1576,21 @@
});
// s2 still cached by owner1. s4 wasn't cached by owner0 so didn't get removed.
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s2", "s4");
+ "s2", "s4", "s5", "s6");
// uncache a non-dynamic shortcut. Should be removed.
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s4"),
HANDLE_USER_0, CACHE_OWNER_1);
});
+
+ // uncache s6 by its only owner. s5 still cached by owner1
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s5", "s6"),
+ HANDLE_USER_0, CACHE_OWNER_2);
+ });
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s2");
+ "s2", "s5");
// Cache another shortcut
runWithCaller(LAUNCHER_1, USER_0, () -> {
@@ -1590,14 +1598,14 @@
HANDLE_USER_0, CACHE_OWNER_0);
});
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s2", "s3");
+ "s2", "s3", "s5");
// Remove a dynamic cached pinned long lived shortcut
mManager.removeLongLivedShortcuts(list("s2"));
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
- "s1", "s3");
+ "s1", "s3", "s6");
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
- "s3");
+ "s3", "s5");
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED),
"s2");
}
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 03e60af..ddbe81c 100644
--- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.powerstats;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -34,6 +35,9 @@
import com.android.server.SystemService;
import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
+import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
+import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerUtils;
+import com.android.server.powerstats.ProtoStreamUtils.PowerEntityUtils;
import com.android.server.powerstats.nano.PowerEntityProto;
import com.android.server.powerstats.nano.PowerStatsServiceMeterProto;
import com.android.server.powerstats.nano.PowerStatsServiceModelProto;
@@ -52,6 +56,7 @@
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
+import java.util.Arrays;
import java.util.Random;
/**
@@ -63,15 +68,18 @@
public class PowerStatsServiceTest {
private static final String TAG = PowerStatsServiceTest.class.getSimpleName();
private static final String DATA_STORAGE_SUBDIR = "powerstatstest";
- private static final String METER_FILENAME = "metertest";
- private static final String MODEL_FILENAME = "modeltest";
- private static final String RESIDENCY_FILENAME = "residencytest";
+ private static final String METER_FILENAME = "log.powerstats.metertest.0";
+ private static final String MODEL_FILENAME = "log.powerstats.modeltest.0";
+ private static final String RESIDENCY_FILENAME = "log.powerstats.residencytest.0";
private static final String PROTO_OUTPUT_FILENAME = "powerstats.proto";
private static final String CHANNEL_NAME = "channelname";
private static final String CHANNEL_SUBSYSTEM = "channelsubsystem";
private static final String POWER_ENTITY_NAME = "powerentityinfo";
private static final String STATE_NAME = "stateinfo";
private static final String ENERGY_CONSUMER_NAME = "energyconsumer";
+ private static final String METER_CACHE_FILENAME = "meterCacheTest";
+ private static final String MODEL_CACHE_FILENAME = "modelCacheTest";
+ private static final String RESIDENCY_CACHE_FILENAME = "residencyCacheTest";
private static final int ENERGY_METER_COUNT = 8;
private static final int ENERGY_CONSUMER_COUNT = 2;
private static final int ENERGY_CONSUMER_ATTRIBUTION_COUNT = 5;
@@ -90,12 +98,12 @@
private TestPowerStatsHALWrapper mTestPowerStatsHALWrapper = new TestPowerStatsHALWrapper();
@Override
File createDataStoragePath() {
- mDataStorageDir = null;
-
- try {
- mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile();
- } catch (IOException e) {
- fail("Could not create temp directory.");
+ if (mDataStorageDir == null) {
+ try {
+ mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile();
+ } catch (IOException e) {
+ fail("Could not create temp directory.");
+ }
}
return mDataStorageDir;
@@ -117,16 +125,36 @@
}
@Override
+ String createMeterCacheFilename() {
+ return METER_CACHE_FILENAME;
+ }
+
+ @Override
+ String createModelCacheFilename() {
+ return MODEL_CACHE_FILENAME;
+ }
+
+ @Override
+ String createResidencyCacheFilename() {
+ return RESIDENCY_CACHE_FILENAME;
+ }
+
+ @Override
IPowerStatsHALWrapper getPowerStatsHALWrapperImpl() {
return mTestPowerStatsHALWrapper;
}
@Override
PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
- String meterFilename, String modelFilename, String residencyFilename,
+ String meterFilename, String meterCacheFilename,
+ String modelFilename, String modelCacheFilename,
+ String residencyFilename, String residencyCacheFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
- mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, meterFilename,
- modelFilename, residencyFilename, powerStatsHALWrapper);
+ mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath,
+ meterFilename, meterCacheFilename,
+ modelFilename, modelCacheFilename,
+ residencyFilename, residencyCacheFilename,
+ powerStatsHALWrapper);
return mPowerStatsLogger;
}
@@ -665,4 +693,315 @@
// input buffer had only length and no data.
assertTrue(pssProto.stateResidencyResult.length == 0);
}
+
+ @Test
+ public void testDataStorageDeletedMeterMismatch() throws IOException {
+ // Create the directory where cached data will be stored.
+ mInjector.createDataStoragePath();
+
+ // In order to create cached data that will match the current data read by the
+ // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+ // returned from the Injector.
+ IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+ // Generate random array of bytes to emulate cached meter data. Store to file.
+ Random rd = new Random();
+ byte[] bytes = new byte[100];
+ rd.nextBytes(bytes);
+ File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+ FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Create cached energy consumer data and write to file.
+ EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+ bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+ onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+ onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Create cached power entity info data and write to file.
+ PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+ bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+ onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+ onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Create log files.
+ File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+ File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+ File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+ meterFile.createNewFile();
+ modelFile.createNewFile();
+ residencyFile.createNewFile();
+
+ // Verify log files exist.
+ assertTrue(meterFile.exists());
+ assertTrue(modelFile.exists());
+ assertTrue(residencyFile.exists());
+
+ // Boot device after creating old cached data.
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ // Since cached meter data is just random bytes it won't match the data read from the HAL.
+ // This mismatch of cached and current HAL data should force a delete.
+ assertTrue(mService.getDeleteMeterDataOnBoot());
+ assertFalse(mService.getDeleteModelDataOnBoot());
+ assertFalse(mService.getDeleteResidencyDataOnBoot());
+
+ // Verify log files were deleted.
+ assertFalse(meterFile.exists());
+ assertTrue(modelFile.exists());
+ assertTrue(residencyFile.exists());
+
+ // Verify cached meter data was updated to new HAL output.
+ Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+ byte[] bytesExpected = ChannelUtils.getProtoBytes(channels);
+ onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+ byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
+ FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
+ onDeviceStorageFis.read(bytesActual);
+ assertTrue(Arrays.equals(bytesExpected, bytesActual));
+ }
+
+ @Test
+ public void testDataStorageDeletedModelMismatch() throws IOException {
+ // Create the directory where cached data will be stored.
+ mInjector.createDataStoragePath();
+
+ // In order to create cached data that will match the current data read by the
+ // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+ // returned from the Injector.
+ IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+ // Create cached channel data and write to file.
+ Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+ byte[] bytes = ChannelUtils.getProtoBytes(channels);
+ File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+ FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Generate random array of bytes to emulate cached energy consumer data. Store to file.
+ Random rd = new Random();
+ bytes = new byte[100];
+ rd.nextBytes(bytes);
+ onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+ onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Create cached power entity info data and write to file.
+ PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+ bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+ onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+ onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Create log files.
+ File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+ File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+ File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+ meterFile.createNewFile();
+ modelFile.createNewFile();
+ residencyFile.createNewFile();
+
+ // Verify log files exist.
+ assertTrue(meterFile.exists());
+ assertTrue(modelFile.exists());
+ assertTrue(residencyFile.exists());
+
+ // Boot device after creating old cached data.
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ // Since cached energy consumer data is just random bytes it won't match the data read from
+ // the HAL. This mismatch of cached and current HAL data should force a delete.
+ assertFalse(mService.getDeleteMeterDataOnBoot());
+ assertTrue(mService.getDeleteModelDataOnBoot());
+ assertFalse(mService.getDeleteResidencyDataOnBoot());
+
+ // Verify log files were deleted.
+ assertTrue(meterFile.exists());
+ assertFalse(modelFile.exists());
+ assertTrue(residencyFile.exists());
+
+ // Verify cached energy consumer data was updated to new HAL output.
+ EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+ byte[] bytesExpected = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+ onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+ byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
+ FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
+ onDeviceStorageFis.read(bytesActual);
+ assertTrue(Arrays.equals(bytesExpected, bytesActual));
+ }
+
+ @Test
+ public void testDataStorageDeletedResidencyMismatch() throws IOException {
+ // Create the directory where cached data will be stored.
+ mInjector.createDataStoragePath();
+
+ // In order to create cached data that will match the current data read by the
+ // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+ // returned from the Injector.
+ IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+ // Create cached channel data and write to file.
+ Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+ byte[] bytes = ChannelUtils.getProtoBytes(channels);
+ File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+ FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Create cached energy consumer data and write to file.
+ EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+ bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+ onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+ onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Generate random array of bytes to emulate cached power entity info data. Store to file.
+ Random rd = new Random();
+ bytes = new byte[100];
+ rd.nextBytes(bytes);
+ onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+ onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Create log files.
+ File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+ File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+ File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+ meterFile.createNewFile();
+ modelFile.createNewFile();
+ residencyFile.createNewFile();
+
+ // Verify log files exist.
+ assertTrue(meterFile.exists());
+ assertTrue(modelFile.exists());
+ assertTrue(residencyFile.exists());
+
+ // Boot device after creating old cached data.
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ // Since cached power entity info data is just random bytes it won't match the data read
+ // from the HAL. This mismatch of cached and current HAL data should force a delete.
+ assertFalse(mService.getDeleteMeterDataOnBoot());
+ assertFalse(mService.getDeleteModelDataOnBoot());
+ assertTrue(mService.getDeleteResidencyDataOnBoot());
+
+ // Verify log files were deleted.
+ assertTrue(meterFile.exists());
+ assertTrue(modelFile.exists());
+ assertFalse(residencyFile.exists());
+
+ // Verify cached power entity data was updated to new HAL output.
+ PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+ byte[] bytesExpected = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+ onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+ byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
+ FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
+ onDeviceStorageFis.read(bytesActual);
+ assertTrue(Arrays.equals(bytesExpected, bytesActual));
+ }
+
+ @Test
+ public void testDataStorageNotDeletedNoCachedData() throws IOException {
+ // Create the directory where log files will be stored.
+ mInjector.createDataStoragePath();
+
+ // Create log files.
+ File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+ File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+ File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+ meterFile.createNewFile();
+ modelFile.createNewFile();
+ residencyFile.createNewFile();
+
+ // Verify log files exist.
+ assertTrue(meterFile.exists());
+ assertTrue(modelFile.exists());
+ assertTrue(residencyFile.exists());
+
+ // This test mimics the device's first boot where there is no cached data.
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ // Since there is no cached data on the first boot any log files that happen to exist
+ // should be deleted.
+ assertTrue(mService.getDeleteMeterDataOnBoot());
+ assertTrue(mService.getDeleteModelDataOnBoot());
+ assertTrue(mService.getDeleteResidencyDataOnBoot());
+
+ // Verify log files were deleted.
+ assertFalse(meterFile.exists());
+ assertFalse(modelFile.exists());
+ assertFalse(residencyFile.exists());
+ }
+
+ @Test
+ public void testDataStorageNotDeletedAllDataMatches() throws IOException {
+ // Create the directory where cached data will be stored.
+ mInjector.createDataStoragePath();
+
+ // In order to create cached data that will match the current data read by the
+ // PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
+ // returned from the Injector.
+ IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
+
+ // Create cached channel data and write to file.
+ Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
+ byte[] bytes = ChannelUtils.getProtoBytes(channels);
+ File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
+ FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Create cached energy consumer data and write to file.
+ EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
+ bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
+ onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
+ onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Create cached power entity info data and write to file.
+ PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
+ bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
+ onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
+ onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Create log files.
+ File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
+ File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
+ File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
+ meterFile.createNewFile();
+ modelFile.createNewFile();
+ residencyFile.createNewFile();
+
+ // Verify log files exist.
+ assertTrue(meterFile.exists());
+ assertTrue(modelFile.exists());
+ assertTrue(residencyFile.exists());
+
+ // Boot device after creating old cached data.
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ // All cached data created above should match current data read in PowerStatsService so we
+ // expect the data not to be deleted.
+ assertFalse(mService.getDeleteMeterDataOnBoot());
+ assertFalse(mService.getDeleteModelDataOnBoot());
+ assertFalse(mService.getDeleteResidencyDataOnBoot());
+
+ // Verify log files were not deleted.
+ assertTrue(meterFile.exists());
+ assertTrue(modelFile.exists());
+ assertTrue(residencyFile.exists());
+ }
}
diff --git a/services/texttospeech/Android.bp b/services/texttospeech/Android.bp
new file mode 100644
index 0000000..bacc932
--- /dev/null
+++ b/services/texttospeech/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+ name: "services.texttospeech-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.texttospeech",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.texttospeech-sources"],
+ libs: ["services.core"],
+}
\ No newline at end of file
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
new file mode 100644
index 0000000..f805904
--- /dev/null
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
@@ -0,0 +1,184 @@
+/*
+ * 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.server.texttospeech;
+
+import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.speech.tts.ITextToSpeechService;
+import android.speech.tts.ITextToSpeechSession;
+import android.speech.tts.ITextToSpeechSessionCallback;
+import android.speech.tts.TextToSpeech;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.ServiceConnector;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Manages per-user text to speech session activated by {@link TextToSpeechManagerService}.
+ * Creates {@link TtsClient} interface object with direct connection to
+ * {@link android.speech.tts.TextToSpeechService} provider.
+ *
+ * @see ITextToSpeechSession
+ * @see TextToSpeech
+ */
+final class TextToSpeechManagerPerUserService extends
+ AbstractPerUserSystemService<TextToSpeechManagerPerUserService,
+ TextToSpeechManagerService> {
+
+ private static final String TAG = TextToSpeechManagerPerUserService.class.getSimpleName();
+
+ TextToSpeechManagerPerUserService(
+ @NonNull TextToSpeechManagerService master,
+ @NonNull Object lock, @UserIdInt int userId) {
+ super(master, lock, userId);
+ }
+
+ void createSessionLocked(String engine, ITextToSpeechSessionCallback sessionCallback) {
+ TextToSpeechSessionConnection.start(getContext(), mUserId, engine, sessionCallback);
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ @NonNull
+ protected ServiceInfo newServiceInfoLocked(
+ @SuppressWarnings("unused") @NonNull ComponentName serviceComponent)
+ throws PackageManager.NameNotFoundException {
+ try {
+ return AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new PackageManager.NameNotFoundException(
+ "Could not get service for " + serviceComponent);
+ }
+ }
+
+ private static class TextToSpeechSessionConnection extends
+ ServiceConnector.Impl<ITextToSpeechService> {
+
+ private final String mEngine;
+ private final ITextToSpeechSessionCallback mCallback;
+ private final DeathRecipient mUnbindOnDeathHandler;
+
+ static void start(Context context, @UserIdInt int userId, String engine,
+ ITextToSpeechSessionCallback callback) {
+ new TextToSpeechSessionConnection(context, userId, engine, callback).start();
+ }
+
+ private TextToSpeechSessionConnection(Context context, @UserIdInt int userId, String engine,
+ ITextToSpeechSessionCallback callback) {
+ super(context,
+ new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE).setPackage(engine),
+ Context.BIND_AUTO_CREATE,
+ userId,
+ ITextToSpeechService.Stub::asInterface);
+ mEngine = engine;
+ mCallback = callback;
+ mUnbindOnDeathHandler = () -> unbindEngine("client process death is reported");
+ }
+
+ private void start() {
+ Slog.d(TAG, "Trying to start connection to TTS engine: " + mEngine);
+
+ connect()
+ .thenAccept(
+ serviceBinder -> {
+ if (serviceBinder != null) {
+ Slog.d(TAG,
+ "Connected successfully to TTS engine: " + mEngine);
+ try {
+ mCallback.onConnected(new ITextToSpeechSession.Stub() {
+ @Override
+ public void disconnect() {
+ unbindEngine("client disconnection request");
+ }
+ }, serviceBinder.asBinder());
+
+ mCallback.asBinder().linkToDeath(mUnbindOnDeathHandler, 0);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Error notifying the client on connection", ex);
+
+ unbindEngine(
+ "failed communicating with the client - process "
+ + "is dead");
+ }
+ } else {
+ Slog.w(TAG, "Failed to obtain TTS engine binder");
+ runSessionCallbackMethod(
+ () -> mCallback.onError("Failed creating TTS session"));
+ }
+ })
+ .exceptionally(ex -> {
+ Slog.w(TAG, "TTS engine binding error", ex);
+ runSessionCallbackMethod(
+ () -> mCallback.onError(
+ "Failed creating TTS session: " + ex.getCause()));
+
+ return null;
+ });
+ }
+
+ @Override // from ServiceConnector.Impl
+ protected void onServiceConnectionStatusChanged(
+ ITextToSpeechService service, boolean connected) {
+ if (!connected) {
+ Slog.w(TAG, "Disconnected from TTS engine");
+ runSessionCallbackMethod(mCallback::onDisconnected);
+
+ try {
+ mCallback.asBinder().unlinkToDeath(mUnbindOnDeathHandler, 0);
+ } catch (NoSuchElementException ex) {
+ Slog.d(TAG, "The death recipient was not linked.");
+ }
+ }
+ }
+
+ @Override // from ServiceConnector.Impl
+ protected long getAutoDisconnectTimeoutMs() {
+ return PERMANENT_BOUND_TIMEOUT_MS;
+ }
+
+ private void unbindEngine(String reason) {
+ Slog.d(TAG, "Unbinding TTS engine: " + mEngine + ". Reason: " + reason);
+ unbind();
+ }
+ }
+
+ static void runSessionCallbackMethod(ThrowingRunnable callbackRunnable) {
+ try {
+ callbackRunnable.runOrThrow();
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed running callback method", ex);
+ }
+ }
+
+ interface ThrowingRunnable {
+ void runOrThrow() throws RemoteException;
+ }
+}
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
new file mode 100644
index 0000000..9015563
--- /dev/null
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerService.java
@@ -0,0 +1,77 @@
+/*
+ * 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.server.texttospeech;
+
+import static com.android.server.texttospeech.TextToSpeechManagerPerUserService.runSessionCallbackMethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.UserHandle;
+import android.speech.tts.ITextToSpeechManager;
+import android.speech.tts.ITextToSpeechSessionCallback;
+
+import com.android.server.infra.AbstractMasterSystemService;
+
+
+/**
+ * A service that allows secured synthesizing of text to speech audio. Upon request creates a
+ * session
+ * that is managed by {@link TextToSpeechManagerPerUserService}.
+ *
+ * @see ITextToSpeechManager
+ */
+public final class TextToSpeechManagerService extends
+ AbstractMasterSystemService<TextToSpeechManagerService,
+ TextToSpeechManagerPerUserService> {
+
+ private static final String TAG = TextToSpeechManagerService.class.getSimpleName();
+
+ public TextToSpeechManagerService(@NonNull Context context) {
+ super(context, /* serviceNameResolver= */ null,
+ /* disallowProperty = */null);
+ }
+
+ @Override // from SystemService
+ public void onStart() {
+ publishBinderService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE,
+ new TextToSpeechManagerServiceStub());
+ }
+
+ @Override
+ protected TextToSpeechManagerPerUserService newServiceLocked(
+ @UserIdInt int resolvedUserId, boolean disabled) {
+ return new TextToSpeechManagerPerUserService(this, mLock, resolvedUserId);
+ }
+
+ private final class TextToSpeechManagerServiceStub extends ITextToSpeechManager.Stub {
+ @Override
+ public void createSession(String engine,
+ ITextToSpeechSessionCallback sessionCallback) {
+ synchronized (mLock) {
+ TextToSpeechManagerPerUserService perUserService = getServiceForUserLocked(
+ UserHandle.getCallingUserId());
+ if (perUserService != null) {
+ perUserService.createSessionLocked(engine, sessionCallback);
+ } else {
+ runSessionCallbackMethod(
+ () -> sessionCallback.onError("Service is not available for user"));
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/ILocalWallpaperColorConsumer.aidl b/telecomm/java/android/telecom/CallScreeningService.aidl
similarity index 69%
rename from core/java/android/app/ILocalWallpaperColorConsumer.aidl
rename to telecomm/java/android/telecom/CallScreeningService.aidl
index 28b11ec..87b5138 100644
--- a/core/java/android/app/ILocalWallpaperColorConsumer.aidl
+++ b/telecomm/java/android/telecom/CallScreeningService.aidl
@@ -11,17 +11,12 @@
* 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.
+ * limitations under the License
*/
-package android.app;
-
-import android.app.WallpaperColors;
-import android.graphics.RectF;
+package android.telecom;
/**
- * @hide
+ * {@hide}
*/
-oneway interface ILocalWallpaperColorConsumer {
- void onColorsChanged(in RectF area, in WallpaperColors colors);
-}
\ No newline at end of file
+parcelable CallScreeningService.ParcelableCallResponse;
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 7988b03..deeb433 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -17,6 +17,7 @@
package android.telecom;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
@@ -30,12 +31,18 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.RemoteException;
import com.android.internal.os.SomeArgs;
import com.android.internal.telecom.ICallScreeningAdapter;
import com.android.internal.telecom.ICallScreeningService;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
/**
* This service can be implemented by the default dialer (see
* {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
@@ -132,7 +139,10 @@
.createFromParcelableCall((ParcelableCall) args.arg2);
onScreenCall(callDetails);
if (callDetails.getCallDirection() == Call.Details.DIRECTION_OUTGOING) {
- mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
+ mCallScreeningAdapter.onScreeningResponse(
+ callDetails.getTelecomCallId(),
+ new ComponentName(getPackageName(), getClass().getName()),
+ null);
}
} catch (RemoteException e) {
Log.w(this, "Exception when screening call: " + e);
@@ -157,10 +167,11 @@
private ICallScreeningAdapter mCallScreeningAdapter;
- /*
- * Information about how to respond to an incoming call.
+ /**
+ * Parcelable version of {@link CallResponse} used to do IPC.
+ * @hide
*/
- public static class CallResponse {
+ public static class ParcelableCallResponse implements Parcelable {
private final boolean mShouldDisallowCall;
private final boolean mShouldRejectCall;
private final boolean mShouldSilenceCall;
@@ -168,13 +179,168 @@
private final boolean mShouldSkipNotification;
private final boolean mShouldScreenCallViaAudioProcessing;
+ private final int mCallComposerAttachmentsToShow;
+
+ private ParcelableCallResponse(
+ boolean shouldDisallowCall,
+ boolean shouldRejectCall,
+ boolean shouldSilenceCall,
+ boolean shouldSkipCallLog,
+ boolean shouldSkipNotification,
+ boolean shouldScreenCallViaAudioProcessing,
+ int callComposerAttachmentsToShow) {
+ mShouldDisallowCall = shouldDisallowCall;
+ mShouldRejectCall = shouldRejectCall;
+ mShouldSilenceCall = shouldSilenceCall;
+ mShouldSkipCallLog = shouldSkipCallLog;
+ mShouldSkipNotification = shouldSkipNotification;
+ mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing;
+ mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
+ }
+
+ protected ParcelableCallResponse(Parcel in) {
+ mShouldDisallowCall = in.readBoolean();
+ mShouldRejectCall = in.readBoolean();
+ mShouldSilenceCall = in.readBoolean();
+ mShouldSkipCallLog = in.readBoolean();
+ mShouldSkipNotification = in.readBoolean();
+ mShouldScreenCallViaAudioProcessing = in.readBoolean();
+ mCallComposerAttachmentsToShow = in.readInt();
+ }
+
+ public CallResponse toCallResponse() {
+ return new CallResponse.Builder()
+ .setDisallowCall(mShouldDisallowCall)
+ .setRejectCall(mShouldRejectCall)
+ .setSilenceCall(mShouldSilenceCall)
+ .setSkipCallLog(mShouldSkipCallLog)
+ .setSkipNotification(mShouldSkipNotification)
+ .setShouldScreenCallViaAudioProcessing(mShouldScreenCallViaAudioProcessing)
+ .setCallComposerAttachmentsToShow(mCallComposerAttachmentsToShow)
+ .build();
+ }
+
+ public boolean shouldDisallowCall() {
+ return mShouldDisallowCall;
+ }
+
+ public boolean shouldRejectCall() {
+ return mShouldRejectCall;
+ }
+
+ public boolean shouldSilenceCall() {
+ return mShouldSilenceCall;
+ }
+
+ public boolean shouldSkipCallLog() {
+ return mShouldSkipCallLog;
+ }
+
+ public boolean shouldSkipNotification() {
+ return mShouldSkipNotification;
+ }
+
+ public boolean shouldScreenCallViaAudioProcessing() {
+ return mShouldScreenCallViaAudioProcessing;
+ }
+
+ public int getCallComposerAttachmentsToShow() {
+ return mCallComposerAttachmentsToShow;
+ }
+
+ public static final Creator<ParcelableCallResponse> CREATOR =
+ new Creator<ParcelableCallResponse>() {
+ @Override
+ public ParcelableCallResponse createFromParcel(Parcel in) {
+ return new ParcelableCallResponse(in);
+ }
+
+ @Override
+ public ParcelableCallResponse[] newArray(int size) {
+ return new ParcelableCallResponse[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mShouldDisallowCall);
+ dest.writeBoolean(mShouldRejectCall);
+ dest.writeBoolean(mShouldSilenceCall);
+ dest.writeBoolean(mShouldSkipCallLog);
+ dest.writeBoolean(mShouldSkipNotification);
+ dest.writeBoolean(mShouldScreenCallViaAudioProcessing);
+ dest.writeInt(mCallComposerAttachmentsToShow);
+ }
+ }
+
+ /**
+ * Information about how to respond to an incoming call. Call screening apps can construct an
+ * instance of this class using {@link CallResponse.Builder}.
+ */
+ public static class CallResponse {
+ /**
+ * Bit flag indicating whether to show the picture attachment for call composer.
+ *
+ * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+ */
+ public static final int CALL_COMPOSER_ATTACHMENT_PICTURE = 1;
+
+ /**
+ * Bit flag indicating whether to show the location attachment for call composer.
+ *
+ * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+ */
+ public static final int CALL_COMPOSER_ATTACHMENT_LOCATION = 1 << 1;
+
+ /**
+ * Bit flag indicating whether to show the subject attachment for call composer.
+ *
+ * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+ */
+ public static final int CALL_COMPOSER_ATTACHMENT_SUBJECT = 1 << 2;
+
+ /**
+ * Bit flag indicating whether to show the priority attachment for call composer.
+ *
+ * Used with {@link Builder#setCallComposerAttachmentsToShow(int)}.
+ */
+ public static final int CALL_COMPOSER_ATTACHMENT_PRIORITY = 1 << 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "CALL_COMPOSER_ATTACHMENT_", flag = true,
+ value = {
+ CALL_COMPOSER_ATTACHMENT_PICTURE,
+ CALL_COMPOSER_ATTACHMENT_LOCATION,
+ CALL_COMPOSER_ATTACHMENT_SUBJECT,
+ CALL_COMPOSER_ATTACHMENT_PRIORITY
+ }
+ )
+ public @interface CallComposerAttachmentType {}
+
+ private static final int NUM_CALL_COMPOSER_ATTACHMENT_TYPES = 4;
+
+ private final boolean mShouldDisallowCall;
+ private final boolean mShouldRejectCall;
+ private final boolean mShouldSilenceCall;
+ private final boolean mShouldSkipCallLog;
+ private final boolean mShouldSkipNotification;
+ private final boolean mShouldScreenCallViaAudioProcessing;
+ private final int mCallComposerAttachmentsToShow;
+
private CallResponse(
boolean shouldDisallowCall,
boolean shouldRejectCall,
boolean shouldSilenceCall,
boolean shouldSkipCallLog,
boolean shouldSkipNotification,
- boolean shouldScreenCallViaAudioProcessing) {
+ boolean shouldScreenCallViaAudioProcessing,
+ int callComposerAttachmentsToShow) {
if (!shouldDisallowCall
&& (shouldRejectCall || shouldSkipCallLog || shouldSkipNotification)) {
throw new IllegalStateException("Invalid response state for allowed call.");
@@ -190,6 +356,7 @@
mShouldSkipNotification = shouldSkipNotification;
mShouldSilenceCall = shouldSilenceCall;
mShouldScreenCallViaAudioProcessing = shouldScreenCallViaAudioProcessing;
+ mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
}
/*
@@ -237,6 +404,49 @@
return mShouldScreenCallViaAudioProcessing;
}
+ /**
+ * @return A bitmask of call composer attachments that should be shown to the user.
+ */
+ public @CallComposerAttachmentType int getCallComposerAttachmentsToShow() {
+ return mCallComposerAttachmentsToShow;
+ }
+
+ /** @hide */
+ public ParcelableCallResponse toParcelable() {
+ return new ParcelableCallResponse(
+ mShouldDisallowCall,
+ mShouldRejectCall,
+ mShouldSilenceCall,
+ mShouldSkipCallLog,
+ mShouldSkipNotification,
+ mShouldScreenCallViaAudioProcessing,
+ mCallComposerAttachmentsToShow
+ );
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ CallResponse that = (CallResponse) o;
+ return mShouldDisallowCall == that.mShouldDisallowCall &&
+ mShouldRejectCall == that.mShouldRejectCall &&
+ mShouldSilenceCall == that.mShouldSilenceCall &&
+ mShouldSkipCallLog == that.mShouldSkipCallLog &&
+ mShouldSkipNotification == that.mShouldSkipNotification &&
+ mShouldScreenCallViaAudioProcessing
+ == that.mShouldScreenCallViaAudioProcessing &&
+ mCallComposerAttachmentsToShow == that.mCallComposerAttachmentsToShow;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mShouldDisallowCall, mShouldRejectCall, mShouldSilenceCall,
+ mShouldSkipCallLog, mShouldSkipNotification,
+ mShouldScreenCallViaAudioProcessing,
+ mCallComposerAttachmentsToShow);
+ }
+
public static class Builder {
private boolean mShouldDisallowCall;
private boolean mShouldRejectCall;
@@ -244,6 +454,7 @@
private boolean mShouldSkipCallLog;
private boolean mShouldSkipNotification;
private boolean mShouldScreenCallViaAudioProcessing;
+ private int mCallComposerAttachmentsToShow = -1;
/**
* Sets whether the incoming call should be blocked.
@@ -329,6 +540,38 @@
return this;
}
+ /**
+ * Sets the call composer attachments that should be shown to the user.
+ *
+ * Attachments that are not shown will not be passed to the in-call UI responsible for
+ * displaying the call to the user.
+ *
+ * If this method is not called on a {@link Builder}, all attachments will be shown,
+ * except pictures, which will only be shown to users if the call is from a contact.
+ *
+ * Setting attachments to show will have no effect if the call screening service does
+ * not belong to the same package as the system dialer (as returned by
+ * {@link TelecomManager#getSystemDialerPackage()}).
+ *
+ * @param callComposerAttachmentsToShow A bitmask of call composer attachments to show.
+ */
+ public @NonNull Builder setCallComposerAttachmentsToShow(
+ @CallComposerAttachmentType int callComposerAttachmentsToShow) {
+ // If the argument is less than zero (meaning unset), no-op since the conversion
+ // to/from the parcelable version may call with that value.
+ if (callComposerAttachmentsToShow < 0) {
+ return this;
+ }
+
+ if ((callComposerAttachmentsToShow
+ & (1 << NUM_CALL_COMPOSER_ATTACHMENT_TYPES)) != 0) {
+ throw new IllegalArgumentException("Attachment types must match the ones"
+ + " defined in CallResponse");
+ }
+ mCallComposerAttachmentsToShow = callComposerAttachmentsToShow;
+ return this;
+ }
+
public CallResponse build() {
return new CallResponse(
mShouldDisallowCall,
@@ -336,7 +579,8 @@
mShouldSilenceCall,
mShouldSkipCallLog,
mShouldSkipNotification,
- mShouldScreenCallViaAudioProcessing);
+ mShouldScreenCallViaAudioProcessing,
+ mCallComposerAttachmentsToShow);
}
}
}
@@ -423,21 +667,12 @@
public final void respondToCall(@NonNull Call.Details callDetails,
@NonNull CallResponse response) {
try {
- if (response.getDisallowCall()) {
- mCallScreeningAdapter.disallowCall(
- callDetails.getTelecomCallId(),
- response.getRejectCall(),
- !response.getSkipCallLog(),
- !response.getSkipNotification(),
- new ComponentName(getPackageName(), getClass().getName()));
- } else if (response.getSilenceCall()) {
- mCallScreeningAdapter.silenceCall(callDetails.getTelecomCallId());
- } else if (response.getShouldScreenCallViaAudioProcessing()) {
- mCallScreeningAdapter.screenCallFurther(callDetails.getTelecomCallId());
- } else {
- mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
- }
+ mCallScreeningAdapter.onScreeningResponse(
+ callDetails.getTelecomCallId(),
+ new ComponentName(getPackageName(), getClass().getName()),
+ response.toParcelable());
} catch (RemoteException e) {
+ Log.e(this, e, "Got remote exception when returning response");
}
}
}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 089a948..942a54e 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -3390,11 +3390,20 @@
* {@code true}, {@link #onDisconnect()} will be called soon after
* this is called.
* @param isInContacts Indicates whether the caller is in the user's contacts list.
+ * @param callScreeningResponse The response that was returned from the
+ * {@link CallScreeningService} that handled this call. If no
+ * response was received from a call screening service,
+ * this will be {@code null}.
+ * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the
+ * system dialer. If {@code callScreeningResponse} is
+ * {@code null}, this will be {@code false}.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.READ_CONTACTS)
- public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) { }
+ public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
+ @Nullable CallScreeningService.CallResponse callScreeningResponse,
+ boolean isResponseFromSystemDialer) { }
static String toLogSafePhoneNumber(String number) {
// For unknown number, log empty string.
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 170ed3e..966ece3 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -759,6 +759,8 @@
@Override
public void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts,
+ CallScreeningService.ParcelableCallResponse callScreeningResponse,
+ boolean isResponseFromSystemDialer,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_CALL_FILTERING_COMPLETED);
try {
@@ -766,7 +768,9 @@
args.arg1 = callId;
args.arg2 = isBlocked;
args.arg3 = isInContacts;
- args.arg4 = Log.createSubsession();
+ args.arg4 = callScreeningResponse;
+ args.arg5 = isResponseFromSystemDialer;
+ args.arg6 = Log.createSubsession();
mHandler.obtainMessage(MSG_ON_CALL_FILTERING_COMPLETED, args).sendToTarget();
} finally {
Log.endSession();
@@ -1437,12 +1441,16 @@
case MSG_ON_CALL_FILTERING_COMPLETED: {
SomeArgs args = (SomeArgs) msg.obj;
try {
- Log.continueSession((Session) args.arg4,
+ Log.continueSession((Session) args.arg6,
SESSION_HANDLER + SESSION_CALL_FILTERING_COMPLETED);
String callId = (String) args.arg1;
boolean isBlocked = (boolean) args.arg2;
boolean isInContacts = (boolean) args.arg3;
- onCallFilteringCompleted(callId, isBlocked, isInContacts);
+ CallScreeningService.ParcelableCallResponse callScreeningResponse =
+ (CallScreeningService.ParcelableCallResponse) args.arg4;
+ boolean isResponseFromSystemDialer = (boolean) args.arg5;
+ onCallFilteringCompleted(callId, isBlocked, isInContacts,
+ callScreeningResponse, isResponseFromSystemDialer);
} finally {
args.recycle();
Log.endSession();
@@ -2458,11 +2466,16 @@
}
}
- private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts) {
- Log.i(this, "onCallFilteringCompleted(%b, %b)", isBlocked, isInContacts);
+ private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts,
+ CallScreeningService.ParcelableCallResponse callScreeningResponse,
+ boolean isResponseFromSystemDialer) {
+ Log.i(this, "onCallFilteringCompleted(%s, %b, %b, %s, %b)", callId,
+ isBlocked, isInContacts, callScreeningResponse, isResponseFromSystemDialer);
Connection connection = findConnectionForAction(callId, "onCallFilteringCompleted");
if (connection != null) {
- connection.onCallFilteringCompleted(isBlocked, isInContacts);
+ connection.onCallFilteringCompleted(isBlocked, isInContacts,
+ callScreeningResponse == null ? null : callScreeningResponse.toCallResponse(),
+ isResponseFromSystemDialer);
}
}
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index feb2ca5..6c6097a 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -1204,15 +1204,25 @@
* the results of a contacts lookup for the remote party.
* @param isBlocked Whether call filtering indicates that the call should be blocked
* @param isInContacts Whether the remote party is in the user's contacts
+ * @param callScreeningResponse The response that was returned from the
+ * {@link CallScreeningService} that handled this call. If no
+ * response was received from a call screening service,
+ * this will be {@code null}.
+ * @param isResponseFromSystemDialer Whether {@code callScreeningResponse} was sent from the
+ * system dialer. If {@code callScreeningResponse} is
+ * {@code null}, this will be {@code false}.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.READ_CONTACTS)
- public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) {
+ public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
+ @Nullable CallScreeningService.CallResponse callScreeningResponse,
+ boolean isResponseFromSystemDialer) {
Log.startSession("RC.oCFC", getActiveOwnerInfo());
try {
if (mConnected) {
mConnectionService.onCallFilteringCompleted(mConnectionId, isBlocked, isInContacts,
+ callScreeningResponse.toParcelable(), isResponseFromSystemDialer,
null /*Session.Info*/);
}
} catch (RemoteException ignored) {
diff --git a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
index 83c8f62..0f2e178 100644
--- a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
@@ -17,6 +17,7 @@
package com.android.internal.telecom;
import android.content.ComponentName;
+import android.telecom.CallScreeningService;
/**
* Internal remote callback interface for call screening services.
@@ -26,16 +27,6 @@
* {@hide}
*/
oneway interface ICallScreeningAdapter {
- void allowCall(String callId);
-
- void silenceCall(String callId);
-
- void screenCallFurther(String callId);
-
- void disallowCall(
- String callId,
- boolean shouldReject,
- boolean shouldAddToCallLog,
- boolean shouldShowNotification,
- in ComponentName componentName);
+ void onScreeningResponse(String callId, in ComponentName componentName,
+ in CallScreeningService.ParcelableCallResponse response);
}
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 301c2bb..7599e18 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -20,6 +20,7 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.telecom.CallAudioState;
+import android.telecom.CallScreeningService;
import android.telecom.ConnectionRequest;
import android.telecom.Logging.Session;
import android.telecom.PhoneAccountHandle;
@@ -119,7 +120,8 @@
void sendCallEvent(String callId, String event, in Bundle extras, in Session.Info sessionInfo);
void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts,
- in Session.Info sessionInfo);
+ in CallScreeningService.ParcelableCallResponse callScreeningResponse,
+ boolean isResponseFromSystemDialer, in Session.Info sessionInfo);
void onExtrasChanged(String callId, in Bundle extras, in Session.Info sessionInfo);
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 070fd799..09c07d3 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -32,6 +32,7 @@
import android.telephony.ims.aidl.IImsRcsController;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.telephony.ims.feature.RcsFeature;
import android.util.Log;
import java.lang.annotation.Retention;
@@ -467,7 +468,7 @@
* poll on the network unless there are contacts being queried with stale information.
* <p>
* Be sure to check the availability of this feature using
- * {@link ImsRcsManager#isAvailable(int)} and ensuring
+ * {@link ImsRcsManager#isAvailable(int, int)} and ensuring
* {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
* {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else
* this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
@@ -484,7 +485,8 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE,
+ Manifest.permission.READ_CONTACTS})
public void requestCapabilities(@NonNull List<Uri> contactNumbers,
@NonNull @CallbackExecutor Executor executor,
@NonNull CapabilitiesCallback c) throws ImsException {
@@ -557,7 +559,7 @@
*
* <p>
* Be sure to check the availability of this feature using
- * {@link ImsRcsManager#isAvailable(int)} and ensuring
+ * {@link ImsRcsManager#isAvailable(int, int)} and ensuring
* {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
* {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
* enabled or else this operation will fail with
@@ -571,7 +573,8 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE,
+ Manifest.permission.READ_CONTACTS})
public void requestAvailability(@NonNull Uri contactNumber,
@NonNull @CallbackExecutor Executor executor,
@NonNull CapabilitiesCallback c) throws ImsException {
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 6fbde50..15d19a4 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -125,6 +125,7 @@
public static final String PROVISIONING_URL_KEY = "provisioningUrl";
public static final String BANDWIDTH_SOURCE_MODEM_KEY = "modem";
public static final String BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY = "carrier_config";
+ public static final String BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR_KEY = "bandwidth_estimator";
public static final String RAT_NAME_LTE = "LTE";
public static final String RAT_NAME_NR_NSA = "NR_NSA";
public static final String RAT_NAME_NR_NSA_MMWAVE = "NR_NSA_MMWAVE";
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index d79225f..ec12040 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -16,6 +16,7 @@
package com.android.internal.telephony.uicc;
+import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
@@ -28,6 +29,7 @@
import com.android.telephony.Rlog;
import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
import java.util.List;
/**
@@ -253,13 +255,48 @@
}
if ((b & 0x0f) <= 0x09) {
- ret += (b & 0xf);
+ ret += (b & 0xf);
}
return ret;
}
/**
+ * Encodes a string to be formatted like the EF[ADN] alpha identifier.
+ *
+ * <p>See javadoc for {@link #adnStringFieldToString(byte[], int, int)} for more details on
+ * the relevant specs.
+ *
+ * <p>This will attempt to encode using the GSM 7-bit alphabet but will fallback to UCS-2 if
+ * there are characters that are not supported by it.
+ *
+ * @return the encoded string including the prefix byte necessary to identify the encoding.
+ * @see #adnStringFieldToString(byte[], int, int)
+ */
+ @NonNull
+ public static byte[] stringToAdnStringField(@NonNull String alphaTag) {
+ int septets = GsmAlphabet.countGsmSeptetsUsingTables(alphaTag, false, 0, 0);
+ if (septets != -1) {
+ byte[] ret = new byte[septets];
+ GsmAlphabet.stringToGsm8BitUnpackedField(alphaTag, ret, 0, ret.length);
+ return ret;
+ }
+
+ // Strictly speaking UCS-2 disallows surrogate characters but it's much more complicated to
+ // validate that the string contains only valid UCS-2 characters. Since the read path
+ // in most modern software will decode "UCS-2" by treating it as UTF-16 this should be fine
+ // (e.g. the adnStringFieldToString has done this for a long time on Android). Also there's
+ // already a precedent in SMS applications to ignore the UCS-2/UTF-16 distinction.
+ byte[] alphaTagBytes = alphaTag.getBytes(StandardCharsets.UTF_16BE);
+ byte[] ret = new byte[alphaTagBytes.length + 1];
+ // 0x80 tags the remaining bytes as UCS-2
+ ret[0] = (byte) 0x80;
+ System.arraycopy(alphaTagBytes, 0, ret, 1, alphaTagBytes.length);
+
+ return ret;
+ }
+
+ /**
* Decodes a string field that's formatted like the EF[ADN] alpha
* identifier
*
@@ -309,7 +346,7 @@
ret = new String(data, offset + 1, ucslen * 2, "utf-16be");
} catch (UnsupportedEncodingException ex) {
Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException",
- ex);
+ ex);
}
if (ret != null) {
@@ -342,7 +379,7 @@
len = length - 4;
base = (char) (((data[offset + 2] & 0xFF) << 8) |
- (data[offset + 3] & 0xFF));
+ (data[offset + 3] & 0xFF));
offset += 4;
isucs2 = true;
}
@@ -366,7 +403,7 @@
count++;
ret.append(GsmAlphabet.gsm8BitUnpackedToString(data,
- offset, count));
+ offset, count));
offset += count;
len -= count;
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 2a693eb..1358410 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -66,6 +66,8 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
@@ -169,6 +171,7 @@
import android.net.INetworkMonitorCallbacks;
import android.net.INetworkPolicyListener;
import android.net.INetworkStatsService;
+import android.net.IOnSetOemNetworkPreferenceListener;
import android.net.IQosCallback;
import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
@@ -192,6 +195,7 @@
import android.net.NetworkStackClient;
import android.net.NetworkState;
import android.net.NetworkTestResultParcelable;
+import android.net.OemNetworkPreferences;
import android.net.ProxyInfo;
import android.net.QosCallbackException;
import android.net.QosFilter;
@@ -300,6 +304,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -359,6 +364,7 @@
private static final String WIFI_WOL_IFNAME = "test_wlan_wol";
private static final String VPN_IFNAME = "tun10042";
private static final String TEST_PACKAGE_NAME = "com.android.test.package";
+ private static final int TEST_PACKAGE_UID = 123;
private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
private static final String INTERFACE_NAME = "interface";
@@ -418,6 +424,7 @@
@Mock EthernetManager mEthernetManager;
@Mock NetworkPolicyManager mNetworkPolicyManager;
@Mock KeyStore mKeyStore;
+ @Mock IOnSetOemNetworkPreferenceListener mOnSetOemNetworkPreferenceListener;
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -9422,4 +9429,264 @@
}
fail("TOO_MANY_REQUESTS never thrown");
}
+
+ private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid)
+ throws PackageManager.NameNotFoundException {
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = uid;
+ when(mPackageManager.getApplicationInfo(eq(packageName), anyInt()))
+ .thenReturn(applicationInfo);
+ }
+
+ private void mockHasSystemFeature(@NonNull final String featureName,
+ @NonNull final boolean hasFeature) {
+ when(mPackageManager.hasSystemFeature(eq(featureName)))
+ .thenReturn(hasFeature);
+ }
+
+ private UidRange getNriFirstUidRange(
+ @NonNull final ConnectivityService.NetworkRequestInfo nri) {
+ return nri.mRequests.get(0).networkCapabilities.getUids().iterator().next();
+ }
+
+ private OemNetworkPreferences createDefaultOemNetworkPreferences(
+ @OemNetworkPreferences.OemNetworkPreference final int preference)
+ throws PackageManager.NameNotFoundException {
+ // Arrange PackageManager mocks
+ mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+
+ // Build OemNetworkPreferences object
+ return new OemNetworkPreferences.Builder()
+ .addNetworkPreference(TEST_PACKAGE_NAME, preference)
+ .build();
+ }
+
+ @Test
+ public void testOemNetworkRequestFactoryPreferenceUninitializedThrowsError()
+ throws PackageManager.NameNotFoundException {
+ @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
+
+ // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+ assertThrows(IllegalArgumentException.class,
+ () -> mService.new OemNetworkRequestFactory()
+ .createNrisFromOemNetworkPreferences(
+ createDefaultOemNetworkPreferences(prefToTest)));
+ }
+
+ @Test
+ public void testOemNetworkRequestFactoryPreferenceOemPaid()
+ throws PackageManager.NameNotFoundException {
+ // Expectations
+ final int expectedNumOfNris = 1;
+ final int expectedNumOfRequests = 3;
+
+ @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+
+ // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+ final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+ mService.new OemNetworkRequestFactory()
+ .createNrisFromOemNetworkPreferences(
+ createDefaultOemNetworkPreferences(prefToTest));
+
+ final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+ assertEquals(expectedNumOfNris, nris.size());
+ assertEquals(expectedNumOfRequests, mRequests.size());
+ assertTrue(mRequests.get(0).isListen());
+ assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED));
+ assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED));
+ assertTrue(mRequests.get(1).isRequest());
+ assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID));
+ assertTrue(mRequests.get(2).isRequest());
+ assertTrue(mService.getDefaultRequest().networkCapabilities.equalsNetCapabilities(
+ mRequests.get(2).networkCapabilities));
+ }
+
+ @Test
+ public void testOemNetworkRequestFactoryPreferenceOemPaidNoFallback()
+ throws PackageManager.NameNotFoundException {
+ // Expectations
+ final int expectedNumOfNris = 1;
+ final int expectedNumOfRequests = 2;
+
+ @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+
+ // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+ final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+ mService.new OemNetworkRequestFactory()
+ .createNrisFromOemNetworkPreferences(
+ createDefaultOemNetworkPreferences(prefToTest));
+
+ final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+ assertEquals(expectedNumOfNris, nris.size());
+ assertEquals(expectedNumOfRequests, mRequests.size());
+ assertTrue(mRequests.get(0).isListen());
+ assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED));
+ assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED));
+ assertTrue(mRequests.get(1).isRequest());
+ assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID));
+ }
+
+ @Test
+ public void testOemNetworkRequestFactoryPreferenceOemPaidOnly()
+ throws PackageManager.NameNotFoundException {
+ // Expectations
+ final int expectedNumOfNris = 1;
+ final int expectedNumOfRequests = 1;
+
+ @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+
+ // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+ final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+ mService.new OemNetworkRequestFactory()
+ .createNrisFromOemNetworkPreferences(
+ createDefaultOemNetworkPreferences(prefToTest));
+
+ final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+ assertEquals(expectedNumOfNris, nris.size());
+ assertEquals(expectedNumOfRequests, mRequests.size());
+ assertTrue(mRequests.get(0).isRequest());
+ assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID));
+ }
+
+ @Test
+ public void testOemNetworkRequestFactoryPreferenceOemPrivateOnly()
+ throws PackageManager.NameNotFoundException {
+ // Expectations
+ final int expectedNumOfNris = 1;
+ final int expectedNumOfRequests = 1;
+
+ @OemNetworkPreferences.OemNetworkPreference final int prefToTest =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+ // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+ final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+ mService.new OemNetworkRequestFactory()
+ .createNrisFromOemNetworkPreferences(
+ createDefaultOemNetworkPreferences(prefToTest));
+
+ final List<NetworkRequest> mRequests = nris.iterator().next().mRequests;
+ assertEquals(expectedNumOfNris, nris.size());
+ assertEquals(expectedNumOfRequests, mRequests.size());
+ assertTrue(mRequests.get(0).isRequest());
+ assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PRIVATE));
+ assertFalse(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID));
+ }
+
+ @Test
+ public void testOemNetworkRequestFactoryCreatesCorrectNumOfNris()
+ throws PackageManager.NameNotFoundException {
+ // Expectations
+ final int expectedNumOfNris = 2;
+
+ // Arrange PackageManager mocks
+ final String testPackageName2 = "com.google.apps.dialer";
+ mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+ mockGetApplicationInfo(testPackageName2, TEST_PACKAGE_UID);
+
+ // Build OemNetworkPreferences object
+ final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+ final int testOemPref2 = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+ final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+ .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+ .addNetworkPreference(testPackageName2, testOemPref2)
+ .build();
+
+ // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+ final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+ mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
+
+ assertNotNull(nris);
+ assertEquals(expectedNumOfNris, nris.size());
+ }
+
+ @Test
+ public void testOemNetworkRequestFactoryCorrectlySetsUids()
+ throws PackageManager.NameNotFoundException {
+ // Arrange PackageManager mocks
+ final String testPackageName2 = "com.google.apps.dialer";
+ final int testPackageNameUid2 = 456;
+ mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+ mockGetApplicationInfo(testPackageName2, testPackageNameUid2);
+
+ // Build OemNetworkPreferences object
+ final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+ final int testOemPref2 = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+ final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+ .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+ .addNetworkPreference(testPackageName2, testOemPref2)
+ .build();
+
+ // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+ final List<ConnectivityService.NetworkRequestInfo> nris =
+ new ArrayList<>(
+ mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(
+ pref));
+
+ // Sort by uid to access nris by index
+ nris.sort(Comparator.comparingInt(nri -> getNriFirstUidRange(nri).start));
+ assertEquals(TEST_PACKAGE_UID, getNriFirstUidRange(nris.get(0)).start);
+ assertEquals(TEST_PACKAGE_UID, getNriFirstUidRange(nris.get(0)).stop);
+ assertEquals(testPackageNameUid2, getNriFirstUidRange(nris.get(1)).start);
+ assertEquals(testPackageNameUid2, getNriFirstUidRange(nris.get(1)).stop);
+ }
+
+ @Test
+ public void testOemNetworkRequestFactoryAddsPackagesToCorrectPreference()
+ throws PackageManager.NameNotFoundException {
+ // Expectations
+ final int expectedNumOfNris = 1;
+ final int expectedNumOfAppUids = 2;
+
+ // Arrange PackageManager mocks
+ final String testPackageName2 = "com.google.apps.dialer";
+ final int testPackageNameUid2 = 456;
+ mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID);
+ mockGetApplicationInfo(testPackageName2, testPackageNameUid2);
+
+ // Build OemNetworkPreferences object
+ final int testOemPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+ final OemNetworkPreferences pref = new OemNetworkPreferences.Builder()
+ .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref)
+ .addNetworkPreference(testPackageName2, testOemPref)
+ .build();
+
+ // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
+ final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+ mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
+
+ assertEquals(expectedNumOfNris, nris.size());
+ assertEquals(expectedNumOfAppUids,
+ nris.iterator().next().mRequests.get(0).networkCapabilities.getUids().size());
+ }
+
+ @Test
+ public void testSetOemNetworkPreferenceNullListenerAndPrefParamThrowsNpe() {
+ mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
+ @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+ // Act on ConnectivityService.setOemNetworkPreference()
+ assertThrows(NullPointerException.class,
+ () -> mService.setOemNetworkPreference(
+ null,
+ null));
+ }
+
+ @Test
+ public void testSetOemNetworkPreferenceFailsForNonAutomotive()
+ throws PackageManager.NameNotFoundException, RemoteException {
+ mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false);
+ @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+
+ // Act on ConnectivityService.setOemNetworkPreference()
+ assertThrows(UnsupportedOperationException.class,
+ () -> mService.setOemNetworkPreference(
+ createDefaultOemNetworkPreferences(networkPref),
+ mOnSetOemNetworkPreferenceListener));
+ }
}
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index 7dada9d..1a90fc3 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -26,24 +26,30 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
+import android.net.vcn.VcnManager.VcnStatusCallback;
import android.net.vcn.VcnManager.VcnUnderlyingNetworkPolicyListener;
+import android.os.ParcelUuid;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
+import java.util.UUID;
import java.util.concurrent.Executor;
public class VcnManagerTest {
+ private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
private static final Executor INLINE_EXECUTOR = Runnable::run;
private IVcnManagementService mMockVcnManagementService;
private VcnUnderlyingNetworkPolicyListener mMockPolicyListener;
+ private VcnStatusCallback mMockStatusCallback;
private Context mContext;
private VcnManager mVcnManager;
@@ -52,6 +58,7 @@
public void setUp() {
mMockVcnManagementService = mock(IVcnManagementService.class);
mMockPolicyListener = mock(VcnUnderlyingNetworkPolicyListener.class);
+ mMockStatusCallback = mock(VcnStatusCallback.class);
mContext = getContext();
mVcnManager = new VcnManager(mContext, mMockVcnManagementService);
@@ -132,4 +139,60 @@
public void testGetUnderlyingNetworkPolicyNullLinkProperties() throws Exception {
mVcnManager.getUnderlyingNetworkPolicy(new NetworkCapabilities(), null);
}
+
+ @Test
+ public void testRegisterVcnStatusCallback() throws Exception {
+ mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+
+ ArgumentCaptor<IVcnStatusCallback> captor =
+ ArgumentCaptor.forClass(IVcnStatusCallback.class);
+ verify(mMockVcnManagementService)
+ .registerVcnStatusCallback(eq(SUB_GROUP), captor.capture(), any());
+
+ IVcnStatusCallback callbackWrapper = captor.getValue();
+ callbackWrapper.onEnteredSafeMode();
+ verify(mMockStatusCallback).onEnteredSafeMode();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRegisterVcnStatusCallbackAlreadyRegistered() throws Exception {
+ mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+ mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testRegisterVcnStatusCallbackNullSubscriptionGroup() throws Exception {
+ mVcnManager.registerVcnStatusCallback(null, INLINE_EXECUTOR, mMockStatusCallback);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testRegisterVcnStatusCallbackNullExecutor() throws Exception {
+ mVcnManager.registerVcnStatusCallback(SUB_GROUP, null, mMockStatusCallback);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testRegisterVcnStatusCallbackNullCallback() throws Exception {
+ mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, null);
+ }
+
+ @Test
+ public void testUnregisterVcnStatusCallback() throws Exception {
+ mVcnManager.registerVcnStatusCallback(SUB_GROUP, INLINE_EXECUTOR, mMockStatusCallback);
+
+ mVcnManager.unregisterVcnStatusCallback(mMockStatusCallback);
+
+ verify(mMockVcnManagementService).unregisterVcnStatusCallback(any());
+ }
+
+ @Test
+ public void testUnregisterUnknownVcnStatusCallback() throws Exception {
+ mVcnManager.unregisterVcnStatusCallback(mMockStatusCallback);
+
+ verifyNoMoreInteractions(mMockVcnManagementService);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testUnregisterNullVcnStatusCallback() throws Exception {
+ mVcnManager.unregisterVcnStatusCallback(null);
+ }
}
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index c290bff..124ec30 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -42,9 +43,11 @@
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -52,6 +55,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkCapabilities.Transport;
import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.IVcnStatusCallback;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnConfigTest;
@@ -70,7 +74,9 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.internal.util.LocationPermissionChecker;
+import com.android.server.VcnManagementService.VcnSafeModeCallback;
+import com.android.server.VcnManagementService.VcnStatusCallbackInfo;
import com.android.server.vcn.TelephonySubscriptionTracker;
import com.android.server.vcn.Vcn;
import com.android.server.vcn.VcnContext;
@@ -147,14 +153,17 @@
mock(PersistableBundleUtils.LockingReadWriteHelper.class);
private final TelephonySubscriptionTracker mSubscriptionTracker =
mock(TelephonySubscriptionTracker.class);
+ private final LocationPermissionChecker mLocationPermissionChecker =
+ mock(LocationPermissionChecker.class);
- private final ArgumentCaptor<VcnSafemodeCallback> mSafemodeCallbackCaptor =
- ArgumentCaptor.forClass(VcnSafemodeCallback.class);
+ private final ArgumentCaptor<VcnSafeModeCallback> mSafeModeCallbackCaptor =
+ ArgumentCaptor.forClass(VcnSafeModeCallback.class);
private final VcnManagementService mVcnMgmtSvc;
private final IVcnUnderlyingNetworkPolicyListener mMockPolicyListener =
mock(IVcnUnderlyingNetworkPolicyListener.class);
+ private final IVcnStatusCallback mMockStatusCallback = mock(IVcnStatusCallback.class);
private final IBinder mMockIBinder = mock(IBinder.class);
public VcnManagementServiceTest() throws Exception {
@@ -171,6 +180,7 @@
doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName();
+ doReturn(mMockContext).when(mVcnContext).getContext();
doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper();
doReturn(TEST_UID).when(mMockDeps).getBinderCallingUid();
doReturn(mVcnContext)
@@ -188,6 +198,9 @@
doReturn(mConfigReadWriteHelper)
.when(mMockDeps)
.newPersistableBundleLockingReadWriteHelper(any());
+ doReturn(mLocationPermissionChecker)
+ .when(mMockDeps)
+ .newLocationPermissionChecker(eq(mMockContext));
// Setup VCN instance generation
doAnswer((invocation) -> {
@@ -206,6 +219,7 @@
mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);
doReturn(mMockIBinder).when(mMockPolicyListener).asBinder();
+ doReturn(mMockIBinder).when(mMockStatusCallback).asBinder();
// Make sure the profiles are loaded.
mTestLooper.dispatchAll();
@@ -707,24 +721,138 @@
verify(mMockPolicyListener).onPolicyChanged();
}
- @Test
- public void testVcnSafemodeCallbackOnEnteredSafemode() throws Exception {
- TelephonySubscriptionSnapshot snapshot =
- triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+ private void verifyVcnSafeModeCallback(
+ @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot)
+ throws Exception {
verify(mMockDeps)
.newVcn(
eq(mVcnContext),
- eq(TEST_UUID_1),
+ eq(subGroup),
eq(TEST_VCN_CONFIG),
eq(snapshot),
- mSafemodeCallbackCaptor.capture());
+ mSafeModeCallbackCaptor.capture());
mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
- VcnSafemodeCallback safemodeCallback = mSafemodeCallbackCaptor.getValue();
- safemodeCallback.onEnteredSafemode();
+ VcnSafeModeCallback safeModeCallback = mSafeModeCallbackCaptor.getValue();
+ safeModeCallback.onEnteredSafeMode();
- assertFalse(mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1).isActive());
verify(mMockPolicyListener).onPolicyChanged();
}
+
+ @Test
+ public void testVcnSafeModeCallbackOnEnteredSafeMode() throws Exception {
+ TelephonySubscriptionSnapshot snapshot =
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+
+ verifyVcnSafeModeCallback(TEST_UUID_1, snapshot);
+ }
+
+ private void triggerVcnStatusCallbackOnEnteredSafeMode(
+ @NonNull ParcelUuid subGroup,
+ @NonNull String pkgName,
+ int uid,
+ boolean hasPermissionsforSubGroup,
+ boolean hasLocationPermission)
+ throws Exception {
+ TelephonySubscriptionSnapshot snapshot =
+ triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(subGroup));
+
+ doReturn(hasPermissionsforSubGroup)
+ .when(snapshot)
+ .packageHasPermissionsForSubscriptionGroup(eq(subGroup), eq(pkgName));
+
+ doReturn(hasLocationPermission)
+ .when(mLocationPermissionChecker)
+ .checkLocationPermission(eq(pkgName), any(), eq(uid), any());
+
+ mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName);
+
+ // Trigger systemReady() to set up LocationPermissionChecker
+ mVcnMgmtSvc.systemReady();
+
+ verifyVcnSafeModeCallback(subGroup, snapshot);
+ }
+
+ @Test
+ public void testVcnStatusCallbackOnEnteredSafeModeWithCarrierPrivileges() throws Exception {
+ triggerVcnStatusCallbackOnEnteredSafeMode(
+ TEST_UUID_1,
+ TEST_PACKAGE_NAME,
+ TEST_UID,
+ true /* hasPermissionsforSubGroup */,
+ true /* hasLocationPermission */);
+
+ verify(mMockStatusCallback, times(1)).onEnteredSafeMode();
+ }
+
+ @Test
+ public void testVcnStatusCallbackOnEnteredSafeModeWithoutCarrierPrivileges() throws Exception {
+ triggerVcnStatusCallbackOnEnteredSafeMode(
+ TEST_UUID_1,
+ TEST_PACKAGE_NAME,
+ TEST_UID,
+ false /* hasPermissionsforSubGroup */,
+ true /* hasLocationPermission */);
+
+ verify(mMockStatusCallback, never()).onEnteredSafeMode();
+ }
+
+ @Test
+ public void testVcnStatusCallbackOnEnteredSafeModeWithoutLocationPermission() throws Exception {
+ triggerVcnStatusCallbackOnEnteredSafeMode(
+ TEST_UUID_1,
+ TEST_PACKAGE_NAME,
+ TEST_UID,
+ true /* hasPermissionsforSubGroup */,
+ false /* hasLocationPermission */);
+
+ verify(mMockStatusCallback, never()).onEnteredSafeMode();
+ }
+
+ @Test
+ public void testRegisterVcnStatusCallback() throws Exception {
+ mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+
+ Map<IBinder, VcnStatusCallbackInfo> callbacks = mVcnMgmtSvc.getAllStatusCallbacks();
+ VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder);
+
+ assertNotNull(cbInfo);
+ assertEquals(TEST_UUID_1, cbInfo.mSubGroup);
+ assertEquals(mMockStatusCallback, cbInfo.mCallback);
+ assertEquals(TEST_PACKAGE_NAME, cbInfo.mPkgName);
+ assertEquals(TEST_UID, cbInfo.mUid);
+ verify(mMockIBinder).linkToDeath(eq(cbInfo), anyInt());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRegisterVcnStatusCallbackDuplicate() {
+ mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+ mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+ }
+
+ @Test
+ public void testUnregisterVcnStatusCallback() {
+ mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+ Map<IBinder, VcnStatusCallbackInfo> callbacks = mVcnMgmtSvc.getAllStatusCallbacks();
+ VcnStatusCallbackInfo cbInfo = callbacks.get(mMockIBinder);
+
+ mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback);
+ assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty());
+ verify(mMockIBinder).unlinkToDeath(eq(cbInfo), anyInt());
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testRegisterVcnStatusCallbackInvalidPackage() {
+ doThrow(new SecurityException()).when(mAppOpsMgr).checkPackage(TEST_UID, TEST_PACKAGE_NAME);
+
+ mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_1, mMockStatusCallback, TEST_PACKAGE_NAME);
+ }
+
+ @Test
+ public void testUnregisterVcnStatusCallbackNeverRegistered() {
+ mVcnMgmtSvc.unregisterVcnStatusCallback(mMockStatusCallback);
+
+ assertTrue(mVcnMgmtSvc.getAllStatusCallbacks().isEmpty());
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index e715480..b62a0b8 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -75,8 +75,8 @@
}
@Test
- public void testEnterStateDoesNotCancelSafemodeAlarm() {
- verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+ public void testEnterStateDoesNotCancelSafeModeAlarm() {
+ verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -144,7 +144,7 @@
@Test
public void testChildOpenedRegistersNetwork() throws Exception {
// Verify scheduled but not canceled when entering ConnectedState
- verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+ verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
final VcnChildSessionConfiguration mMockChildSessionConfig =
mock(VcnChildSessionConfiguration.class);
@@ -188,17 +188,17 @@
assertTrue(nc.hasCapability(cap));
}
- // Now that Vcn Network is up, notify it as validated and verify the Safemode alarm is
+ // Now that Vcn Network is up, notify it as validated and verify the SafeMode alarm is
// canceled
mGatewayConnection.mNetworkAgent.onValidationStatus(
NetworkAgent.VALIDATION_STATUS_VALID, null /* redirectUri */);
- verify(mSafemodeTimeoutAlarm).cancel();
+ verify(mSafeModeTimeoutAlarm).cancel();
}
@Test
public void testChildSessionClosedTriggersDisconnect() throws Exception {
// Verify scheduled but not canceled when entering ConnectedState
- verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+ verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
getChildSessionCallback().onClosed();
mTestLooper.dispatchAll();
@@ -206,14 +206,14 @@
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
- // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
- verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
+ // Since network never validated, verify mSafeModeTimeoutAlarm not canceled
+ verifyNoMoreInteractions(mSafeModeTimeoutAlarm);
}
@Test
public void testIkeSessionClosedTriggersDisconnect() throws Exception {
// Verify scheduled but not canceled when entering ConnectedState
- verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+ verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
getIkeSessionCallback().onClosed();
mTestLooper.dispatchAll();
@@ -221,7 +221,7 @@
assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
verify(mIkeSession).close();
- // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
- verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
+ // Since network never validated, verify mSafeModeTimeoutAlarm not canceled
+ verifyNoMoreInteractions(mSafeModeTimeoutAlarm);
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index 07282c9..17ae19e 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -108,7 +108,7 @@
}
@Test
- public void testSafemodeTimeoutNotifiesCallback() {
- verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
+ public void testSafeModeTimeoutNotifiesCallback() {
+ verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index 49ce54d..9ea641f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -79,7 +79,7 @@
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
- verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+ verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -100,6 +100,6 @@
assertNull(mGatewayConnection.getCurrentState());
verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any());
- verifySafemodeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
+ verifySafeModeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
index 22eab2a..7385204 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
@@ -82,7 +82,7 @@
}
@Test
- public void testSafemodeTimeoutNotifiesCallback() {
- verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
+ public void testSafeModeTimeoutNotifiesCallback() {
+ verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
index 6c26075..5b0850b 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
@@ -93,7 +93,7 @@
}
@Test
- public void testSafemodeTimeoutNotifiesCallback() {
- verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
+ public void testSafeModeTimeoutNotifiesCallback() {
+ verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index ac9ec06..a660735 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -110,7 +110,7 @@
@NonNull protected final WakeupMessage mTeardownTimeoutAlarm;
@NonNull protected final WakeupMessage mDisconnectRequestAlarm;
@NonNull protected final WakeupMessage mRetryTimeoutAlarm;
- @NonNull protected final WakeupMessage mSafemodeTimeoutAlarm;
+ @NonNull protected final WakeupMessage mSafeModeTimeoutAlarm;
@NonNull protected final IpSecService mIpSecSvc;
@NonNull protected final ConnectivityManager mConnMgr;
@@ -131,7 +131,7 @@
mTeardownTimeoutAlarm = mock(WakeupMessage.class);
mDisconnectRequestAlarm = mock(WakeupMessage.class);
mRetryTimeoutAlarm = mock(WakeupMessage.class);
- mSafemodeTimeoutAlarm = mock(WakeupMessage.class);
+ mSafeModeTimeoutAlarm = mock(WakeupMessage.class);
mIpSecSvc = mock(IpSecService.class);
setupIpSecManager(mContext, mIpSecSvc);
@@ -154,7 +154,7 @@
setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM);
setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM);
setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM);
- setUpWakeupMessage(mSafemodeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM);
+ setUpWakeupMessage(mSafeModeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM);
doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime();
}
@@ -259,23 +259,23 @@
expectCanceled);
}
- protected Runnable verifySafemodeTimeoutAlarmAndGetCallback(boolean expectCanceled) {
+ protected Runnable verifySafeModeTimeoutAlarmAndGetCallback(boolean expectCanceled) {
return verifyWakeupMessageSetUpAndGetCallback(
VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM,
- mSafemodeTimeoutAlarm,
+ mSafeModeTimeoutAlarm,
TimeUnit.SECONDS.toMillis(VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS),
expectCanceled);
}
- protected void verifySafemodeTimeoutNotifiesCallback(@NonNull State expectedState) {
- // Safemode timer starts when VcnGatewayConnection exits DisconnectedState (the initial
+ protected void verifySafeModeTimeoutNotifiesCallback(@NonNull State expectedState) {
+ // SafeMode timer starts when VcnGatewayConnection exits DisconnectedState (the initial
// state)
final Runnable delayedEvent =
- verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+ verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
delayedEvent.run();
mTestLooper.dispatchAll();
- verify(mGatewayStatusCallback).onEnteredSafemode();
+ verify(mGatewayStatusCallback).onEnteredSafeMode();
assertEquals(expectedState, mGatewayConnection.getCurrentState());
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
index 66cbf84..8e142c0 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -34,7 +34,7 @@
import android.os.ParcelUuid;
import android.os.test.TestLooper;
-import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.server.VcnManagementService.VcnSafeModeCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener;
@@ -56,7 +56,7 @@
private VcnContext mVcnContext;
private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
private VcnNetworkProvider mVcnNetworkProvider;
- private VcnSafemodeCallback mVcnSafemodeCallback;
+ private VcnSafeModeCallback mVcnSafeModeCallback;
private Vcn.Dependencies mDeps;
private ArgumentCaptor<VcnGatewayStatusCallback> mGatewayStatusCallbackCaptor;
@@ -72,7 +72,7 @@
mVcnContext = mock(VcnContext.class);
mSubscriptionSnapshot = mock(TelephonySubscriptionSnapshot.class);
mVcnNetworkProvider = mock(VcnNetworkProvider.class);
- mVcnSafemodeCallback = mock(VcnSafemodeCallback.class);
+ mVcnSafeModeCallback = mock(VcnSafeModeCallback.class);
mDeps = mock(Vcn.Dependencies.class);
mTestLooper = new TestLooper();
@@ -104,7 +104,7 @@
TEST_SUB_GROUP,
mConfig,
mSubscriptionSnapshot,
- mVcnSafemodeCallback,
+ mVcnSafeModeCallback,
mDeps);
}
@@ -148,7 +148,7 @@
}
@Test
- public void testGatewayEnteringSafemodeNotifiesVcn() {
+ public void testGatewayEnteringSafeModeNotifiesVcn() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
startVcnGatewayWithCapabilities(requestListener, capability);
@@ -168,16 +168,17 @@
any(),
mGatewayStatusCallbackCaptor.capture());
- // Doesn't matter which callback this gets - any Gateway entering Safemode should shut down
+ // Doesn't matter which callback this gets - any Gateway entering safe mode should shut down
// all Gateways
final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
- statusCallback.onEnteredSafemode();
+ statusCallback.onEnteredSafeMode();
mTestLooper.dispatchAll();
+ assertFalse(mVcn.isActive());
for (final VcnGatewayConnection gatewayConnection : gatewayConnections) {
verify(gatewayConnection).teardownAsynchronously();
}
verify(mVcnNetworkProvider).unregisterListener(requestListener);
- verify(mVcnSafemodeCallback).onEnteredSafemode();
+ verify(mVcnSafeModeCallback).onEnteredSafeMode();
}
}