diff options
771 files changed, 27483 insertions, 12534 deletions
diff --git a/Android.bp b/Android.bp index 34162d1fcc1d..32bd40861839 100644 --- a/Android.bp +++ b/Android.bp @@ -104,6 +104,7 @@ java_defaults { "core/java/android/app/backup/IRestoreObserver.aidl", "core/java/android/app/backup/IRestoreSession.aidl", "core/java/android/app/backup/ISelectBackupTransportCallback.aidl", + "core/java/android/app/role/IOnRoleHoldersChangedListener.aidl", "core/java/android/app/role/IRoleManager.aidl", "core/java/android/app/role/IRoleManagerCallback.aidl", "core/java/android/app/slice/ISliceManager.aidl", @@ -265,9 +266,12 @@ java_defaults { "core/java/android/os/storage/IStorageEventListener.aidl", "core/java/android/os/storage/IStorageShutdownObserver.aidl", "core/java/android/os/storage/IObbActionListener.aidl", + "core/java/android/permission/IRuntimePermissionPresenter.aidl", "core/java/android/rolecontrollerservice/IRoleControllerService.aidl", ":keystore_aidl", "core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl", + "core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl", + "core/java/android/service/autofill/augmented/IFillCallback.aidl", "core/java/android/service/autofill/IAutoFillService.aidl", "core/java/android/service/autofill/IAutofillFieldClassificationService.aidl", "core/java/android/service/autofill/IFillCallback.aidl", @@ -290,7 +294,7 @@ java_defaults { "core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl", "core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl", "core/java/android/service/gatekeeper/IGateKeeperService.aidl", - "core/java/android/service/intelligence/IIntelligenceService.aidl", + "core/java/android/service/contentcapture/IContentCaptureService.aidl", "core/java/android/service/notification/INotificationListener.aidl", "core/java/android/service/notification/IStatusBarNotificationHolder.aidl", "core/java/android/service/notification/IConditionListener.aidl", @@ -351,7 +355,7 @@ java_defaults { "core/java/android/view/autofill/IAutoFillManagerClient.aidl", "core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl", "core/java/android/view/autofill/IAutofillWindowPresenter.aidl", - "core/java/android/view/intelligence/IIntelligenceManager.aidl", + "core/java/android/view/contentcapture/IContentCaptureManager.aidl", "core/java/android/view/IApplicationToken.aidl", "core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl", "core/java/android/view/IDockedStackListener.aidl", @@ -440,7 +444,6 @@ java_defaults { "location/java/android/location/IGeocodeProvider.aidl", "location/java/android/location/IGeofenceProvider.aidl", "location/java/android/location/IGnssStatusListener.aidl", - "location/java/android/location/IGnssStatusProvider.aidl", "location/java/android/location/IGnssMeasurementsListener.aidl", "location/java/android/location/IGnssNavigationMessageListener.aidl", "location/java/android/location/ILocationListener.aidl", @@ -449,6 +452,7 @@ java_defaults { "location/java/android/location/IGpsGeofenceHardware.aidl", "location/java/android/location/INetInitiatedListener.aidl", "location/java/com/android/internal/location/ILocationProvider.aidl", + "location/java/com/android/internal/location/ILocationProviderManager.aidl", "media/java/android/media/IAudioFocusDispatcher.aidl", "media/java/android/media/IAudioRoutesObserver.aidl", "media/java/android/media/IAudioService.aidl", @@ -631,7 +635,6 @@ java_defaults { ":libupdate_engine_aidl", ":storaged_aidl", - ":netd_aidl", ":vold_aidl", ":installd_aidl", ":dumpstate_aidl", @@ -688,7 +691,6 @@ java_defaults { "frameworks/av/media/libaudioclient/aidl", "frameworks/native/aidl/gui", "system/core/storaged/binder", - "system/netd/server/binder", "system/vold/binder", "system/bt/binder", "system/security/keystore/binder", @@ -782,18 +784,6 @@ java_library { ], } -// A host library containing the inspector annotations for inspector-annotation-processor. -java_library_host { - name: "inspector-annotation", - srcs: [ - "core/java/android/view/inspector/InspectableNodeName.java", - "core/java/android/view/inspector/InspectableProperty.java", - // Needed for the ResourceId.ID_NULL constant - "core/java/android/content/res/ResourceId.java", - "core/java/android/annotation/AnyRes.java", - ], -} - // A host library including just UnsupportedAppUsage.java so that the annotation // processor can also use this annotation. java_library_host { @@ -1615,6 +1605,7 @@ droidstubs { ], dex_mapping_filename: "dex-mapping.txt", args: metalava_framework_docs_args + + " --hide ReferencesHidden " + " --show-unannotated " + " --show-annotation android.annotation.SystemApi " + " --show-annotation android.annotation.TestApi " diff --git a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java index 5be0cb025105..99e4ba1bb804 100644 --- a/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java +++ b/apct-tests/perftests/core/src/android/os/BinderCallsStatsPerfTest.java @@ -39,6 +39,7 @@ import org.junit.runner.RunWith; @LargeTest public class BinderCallsStatsPerfTest { private static final int DEFAULT_BUCKET_SIZE = 1000; + private static final int WORKSOURCE_UID = 1; static class FakeCpuTimeBinderCallsStats extends BinderCallsStats { private int mTimeMs; @@ -117,8 +118,8 @@ public class BinderCallsStatsPerfTest { Binder b = new Binder(); while (state.keepRunning()) { for (int i = 0; i < 10000; i++) { - CallSession s = mBinderCallsStats.callStarted(b, i % maxBucketSize); - mBinderCallsStats.callEnded(s, 0, 0); + CallSession s = mBinderCallsStats.callStarted(b, i % maxBucketSize, WORKSOURCE_UID); + mBinderCallsStats.callEnded(s, 0, 0, WORKSOURCE_UID); } } } diff --git a/api/current.txt b/api/current.txt index f7060f341ace..a769e7f63806 100644 --- a/api/current.txt +++ b/api/current.txt @@ -78,6 +78,7 @@ package android { field public static final java.lang.String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE"; field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS"; field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED"; + field public static final java.lang.String GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY = "android.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY"; field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE"; field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS"; field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH"; @@ -90,6 +91,7 @@ package android { field public static final java.lang.String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE"; field public static final java.lang.String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS"; field public static final java.lang.String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS"; + field public static final java.lang.String CALL_COMPANION_APP = "android.permission.CALL_COMPANION_APP"; field public static final java.lang.String MASTER_CLEAR = "android.permission.MASTER_CLEAR"; field public static final java.lang.String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL"; field public static final java.lang.String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS"; @@ -644,6 +646,7 @@ package android { field public static final int forceHasOverlappingRendering = 16844065; // 0x1010521 field public static final int foreground = 16843017; // 0x1010109 field public static final int foregroundGravity = 16843264; // 0x1010200 + field public static final int foregroundServiceType = 16844191; // 0x101059f field public static final int foregroundTint = 16843885; // 0x101046d field public static final int foregroundTintMode = 16843886; // 0x101046e field public static final int format = 16843013; // 0x1010105 @@ -5947,6 +5950,15 @@ package android.app { field public static final int STYLE_SPINNER = 0; // 0x0 } + public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable { + ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction); + method public int describeContents(); + method public android.app.RemoteAction getUserAction(); + method public java.lang.CharSequence getUserMessage(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR; + } + public final class RemoteAction implements android.os.Parcelable { ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent); method public android.app.RemoteAction clone(); @@ -5972,6 +5984,7 @@ package android.app { method public java.util.Set<java.lang.String> getAllowedDataTypes(); method public java.lang.CharSequence[] getChoices(); method public static java.util.Map<java.lang.String, android.net.Uri> getDataResultsFromIntent(android.content.Intent, java.lang.String); + method public int getEditChoicesBeforeSending(); method public android.os.Bundle getExtras(); method public java.lang.CharSequence getLabel(); method public java.lang.String getResultKey(); @@ -5981,6 +5994,9 @@ package android.app { method public static void setResultsSource(android.content.Intent, int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.RemoteInput> CREATOR; + field public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0; // 0x0 + field public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1; // 0x1 + field public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2; // 0x2 field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; field public static final java.lang.String RESULTS_CLIP_LABEL = "android.remoteinput.results"; field public static final int SOURCE_CHOICE = 1; // 0x1 @@ -5995,6 +6011,7 @@ package android.app { method public android.app.RemoteInput.Builder setAllowDataType(java.lang.String, boolean); method public android.app.RemoteInput.Builder setAllowFreeFormInput(boolean); method public android.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]); + method public android.app.RemoteInput.Builder setEditChoicesBeforeSending(int); method public android.app.RemoteInput.Builder setLabel(java.lang.CharSequence); } @@ -6432,6 +6449,13 @@ package android.app.admin { field public static final android.os.Parcelable.Creator<android.app.admin.ConnectEvent> CREATOR; } + public class DelegatedAdminReceiver extends android.content.BroadcastReceiver { + ctor public DelegatedAdminReceiver(); + method public java.lang.String onChoosePrivateKeyAlias(android.content.Context, android.content.Intent, int, android.net.Uri, java.lang.String); + method public void onNetworkLogsAvailable(android.content.Context, android.content.Intent, long, int); + method public void onReceive(android.content.Context, android.content.Intent); + } + public final class DeviceAdminInfo implements android.os.Parcelable { ctor public DeviceAdminInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public int describeContents(); @@ -6494,11 +6518,13 @@ package android.app.admin { method public void onUserStarted(android.content.Context, android.content.Intent, android.os.UserHandle); method public void onUserStopped(android.content.Context, android.content.Intent, android.os.UserHandle); method public void onUserSwitched(android.content.Context, android.content.Intent, android.os.UserHandle); + field public static final java.lang.String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED"; field public static final java.lang.String ACTION_LOCK_TASK_ENTERING = "android.app.action.LOCK_TASK_ENTERING"; field public static final java.lang.String ACTION_LOCK_TASK_EXITING = "android.app.action.LOCK_TASK_EXITING"; + field public static final java.lang.String ACTION_NETWORK_LOGS_AVAILABLE = "android.app.action.NETWORK_LOGS_AVAILABLE"; field public static final java.lang.String ACTION_PASSWORD_CHANGED = "android.app.action.ACTION_PASSWORD_CHANGED"; field public static final java.lang.String ACTION_PASSWORD_EXPIRING = "android.app.action.ACTION_PASSWORD_EXPIRING"; field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED"; @@ -6572,6 +6598,7 @@ package android.app.admin { method public java.lang.CharSequence getOrganizationName(android.content.ComponentName); method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(android.content.ComponentName); method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName); + method public int getPasswordComplexity(); method public long getPasswordExpiration(android.content.ComponentName); method public long getPasswordExpirationTimeout(android.content.ComponentName); method public int getPasswordHistoryLength(android.content.ComponentName); @@ -6664,7 +6691,7 @@ package android.app.admin { method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>); method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence); method public void setEndUserSessionMessage(android.content.ComponentName, java.lang.CharSequence); - method public void setGlobalPrivateDns(android.content.ComponentName, int, java.lang.String); + method public int setGlobalPrivateDns(android.content.ComponentName, int, java.lang.String); method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public void setKeepUninstalledPackages(android.content.ComponentName, java.util.List<java.lang.String>); method public boolean setKeyPairCertificate(android.content.ComponentName, java.lang.String, java.util.List<java.security.cert.Certificate>, boolean); @@ -6743,10 +6770,13 @@ package android.app.admin { field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions"; field public static final java.lang.String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall"; field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install"; + field public static final java.lang.String DELEGATION_CERT_SELECTION = "delegation-cert-selection"; field public static final java.lang.String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app"; field public static final java.lang.String DELEGATION_INSTALL_EXISTING_PACKAGE = "delegation-install-existing-package"; field public static final java.lang.String DELEGATION_KEEP_UNINSTALLED_PACKAGES = "delegation-keep-uninstalled-packages"; + field public static final java.lang.String DELEGATION_NETWORK_LOGGING = "delegation-network-logging"; field public static final java.lang.String DELEGATION_PACKAGE_ACCESS = "delegation-package-access"; + field public static final java.lang.String DELEGATION_PACKAGE_INSTALLATION = "delegation-package-installation"; field public static final java.lang.String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant"; field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2 field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3 @@ -6825,6 +6855,10 @@ package android.app.admin { field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1 field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2 field public static final java.lang.String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning"; + field public static final int PASSWORD_COMPLEXITY_HIGH = 327680; // 0x50000 + field public static final int PASSWORD_COMPLEXITY_LOW = 65536; // 0x10000 + field public static final int PASSWORD_COMPLEXITY_MEDIUM = 196608; // 0x30000 + field public static final int PASSWORD_COMPLEXITY_NONE = 0; // 0x0 field public static final int PASSWORD_QUALITY_ALPHABETIC = 262144; // 0x40000 field public static final int PASSWORD_QUALITY_ALPHANUMERIC = 327680; // 0x50000 field public static final int PASSWORD_QUALITY_BIOMETRIC_WEAK = 32768; // 0x8000 @@ -6845,6 +6879,9 @@ package android.app.admin { field public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2; // 0x2 field public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3; // 0x3 field public static final int PRIVATE_DNS_MODE_UNKNOWN = 0; // 0x0 + field public static final int PRIVATE_DNS_SET_ERROR_FAILURE_SETTING = 2; // 0x2 + field public static final int PRIVATE_DNS_SET_ERROR_HOST_NOT_SERVING = 1; // 0x1 + field public static final int PRIVATE_DNS_SET_SUCCESS = 0; // 0x0 field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2 field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1 field public static final int SKIP_SETUP_WIZARD = 1; // 0x1 @@ -7610,13 +7647,16 @@ package android.app.usage { method public java.lang.String getPackageName(); method public java.lang.String getShortcutId(); method public long getTimeStamp(); + field public static final int ACTIVITY_PAUSED = 2; // 0x2 + field public static final int ACTIVITY_RESUMED = 1; // 0x1 + field public static final int ACTIVITY_STOPPED = 23; // 0x17 field public static final int CONFIGURATION_CHANGE = 5; // 0x5 field public static final int FOREGROUND_SERVICE_START = 19; // 0x13 field public static final int FOREGROUND_SERVICE_STOP = 20; // 0x14 field public static final int KEYGUARD_HIDDEN = 18; // 0x12 field public static final int KEYGUARD_SHOWN = 17; // 0x11 - field public static final int MOVE_TO_BACKGROUND = 2; // 0x2 - field public static final int MOVE_TO_FOREGROUND = 1; // 0x1 + field public static final deprecated int MOVE_TO_BACKGROUND = 2; // 0x2 + field public static final deprecated int MOVE_TO_FOREGROUND = 1; // 0x1 field public static final int NONE = 0; // 0x0 field public static final int SCREEN_INTERACTIVE = 15; // 0xf field public static final int SCREEN_NON_INTERACTIVE = 16; // 0x10 @@ -7633,9 +7673,11 @@ package android.app.usage { method public long getLastTimeForegroundServiceUsed(); method public long getLastTimeStamp(); method public long getLastTimeUsed(); + method public long getLastTimeVisible(); method public java.lang.String getPackageName(); method public long getTotalTimeForegroundServiceUsed(); method public long getTotalTimeInForeground(); + method public long getTotalTimeVisible(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.usage.UsageStats> CREATOR; } @@ -9227,11 +9269,31 @@ package android.content { field public static final android.os.Parcelable.Creator<android.content.ComponentName> CREATOR; } - public abstract class ContentProvider implements android.content.ComponentCallbacks2 { + public abstract interface ContentInterface { + method public abstract android.content.ContentProviderResult[] applyBatch(java.lang.String, java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException; + method public abstract int bulkInsert(android.net.Uri, android.content.ContentValues[]) throws android.os.RemoteException; + method public abstract android.os.Bundle call(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException; + method public abstract android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException; + method public abstract int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException; + method public abstract java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException; + method public abstract java.lang.String getType(android.net.Uri) throws android.os.RemoteException; + method public abstract android.net.Uri insert(android.net.Uri, android.content.ContentValues) throws android.os.RemoteException; + method public abstract android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; + method public abstract android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; + method public abstract android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; + method public abstract android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal) throws android.os.RemoteException; + method public abstract boolean refresh(android.net.Uri, android.os.Bundle, android.os.CancellationSignal) throws android.os.RemoteException; + method public abstract android.net.Uri uncanonicalize(android.net.Uri) throws android.os.RemoteException; + method public abstract int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) throws android.os.RemoteException; + } + + public abstract class ContentProvider implements android.content.ComponentCallbacks2 android.content.ContentInterface { ctor public ContentProvider(); + method public android.content.ContentProviderResult[] applyBatch(java.lang.String, java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException; method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException; method public void attachInfo(android.content.Context, android.content.pm.ProviderInfo); method public int bulkInsert(android.net.Uri, android.content.ContentValues[]); + method public android.os.Bundle call(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle); method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle); method public android.net.Uri canonicalize(android.net.Uri); method public final android.content.ContentProvider.CallingIdentity clearCallingIdentity(); @@ -9278,10 +9340,12 @@ package android.content { method public abstract void writeDataToPipe(android.os.ParcelFileDescriptor, android.net.Uri, java.lang.String, android.os.Bundle, T); } - public class ContentProviderClient implements java.lang.AutoCloseable { + public class ContentProviderClient implements java.lang.AutoCloseable android.content.ContentInterface { method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException; + method public android.content.ContentProviderResult[] applyBatch(java.lang.String, java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException; method public int bulkInsert(android.net.Uri, android.content.ContentValues[]) throws android.os.RemoteException; method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException; + method public android.os.Bundle call(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException; method public final android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException; method public void close(); method public static void closeQuietly(android.content.ContentProviderClient); @@ -9294,6 +9358,7 @@ package android.content { method public android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException, android.os.RemoteException; method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; + method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException, android.os.RemoteException; method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) throws android.os.RemoteException; @@ -9358,7 +9423,7 @@ package android.content { method public void setKeepUpdated(boolean); } - public abstract class ContentResolver { + public abstract class ContentResolver implements android.content.ContentInterface { ctor public ContentResolver(android.content.Context); method public final android.content.ContentProviderClient acquireContentProviderClient(android.net.Uri); method public final android.content.ContentProviderClient acquireContentProviderClient(java.lang.String); @@ -9369,11 +9434,13 @@ package android.content { method public android.content.ContentProviderResult[] applyBatch(java.lang.String, java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException; method public final int bulkInsert(android.net.Uri, android.content.ContentValues[]); method public final android.os.Bundle call(android.net.Uri, java.lang.String, java.lang.String, android.os.Bundle); + method public final android.os.Bundle call(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle); method public deprecated void cancelSync(android.net.Uri); method public static void cancelSync(android.accounts.Account, java.lang.String); method public static void cancelSync(android.content.SyncRequest); method public final android.net.Uri canonicalize(android.net.Uri); method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); + method public android.os.Bundle getCache(android.net.Uri); method public static deprecated android.content.SyncInfo getCurrentSync(); method public static java.util.List<android.content.SyncInfo> getCurrentSyncs(); method public static int getIsSyncable(android.accounts.Account, java.lang.String); @@ -9392,15 +9459,19 @@ package android.content { method public void notifyChange(android.net.Uri, android.database.ContentObserver); method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean); method public void notifyChange(android.net.Uri, android.database.ContentObserver, int); + method public final android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; + method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final java.io.InputStream openInputStream(android.net.Uri) throws java.io.FileNotFoundException; method public final java.io.OutputStream openOutputStream(android.net.Uri) throws java.io.FileNotFoundException; method public final java.io.OutputStream openOutputStream(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; + method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; + method public void putCache(android.net.Uri, android.os.Bundle); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal); @@ -10205,6 +10276,7 @@ package android.content { field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD"; field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE"; field public static final java.lang.String EXTRA_ASSIST_UID = "android.intent.extra.ASSIST_UID"; + field public static final java.lang.String EXTRA_AUTO_LAUNCH_SINGLE_CHOICE = "android.intent.extra.AUTO_LAUNCH_SINGLE_CHOICE"; field public static final java.lang.String EXTRA_BCC = "android.intent.extra.BCC"; field public static final java.lang.String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT"; field public static final java.lang.String EXTRA_CC = "android.intent.extra.CC"; @@ -11201,6 +11273,7 @@ package android.content.pm { method public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllSessions(); method public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getMySessions(); method public android.content.pm.PackageInstaller.SessionInfo getSessionInfo(int); + method public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getStagedSessions(); method public android.content.pm.PackageInstaller.Session openSession(int) throws java.io.IOException; method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback); method public void registerSessionCallback(android.content.pm.PackageInstaller.SessionCallback, android.os.Handler); @@ -11239,6 +11312,7 @@ package android.content.pm { method public java.lang.String[] getNames() throws java.io.IOException; method public int getParentSessionId(); method public boolean isMultiPackage(); + method public boolean isStaged(); method public java.io.InputStream openRead(java.lang.String) throws java.io.IOException; method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException; method public void removeChildSessionId(int); @@ -11277,6 +11351,7 @@ package android.content.pm { method public boolean isActive(); method public boolean isMultiPackage(); method public boolean isSealed(); + method public boolean isStaged(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionInfo> CREATOR; field public static final int INVALID_ID = -1; // 0xffffffff @@ -11295,6 +11370,7 @@ package android.content.pm { method public void setOriginatingUri(android.net.Uri); method public void setReferrerUri(android.net.Uri); method public void setSize(long); + method public void setStaged(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionParams> CREATOR; field public static final int MODE_FULL_INSTALL = 1; // 0x1 @@ -11479,6 +11555,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors"; field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen"; field public static final java.lang.String FEATURE_INPUT_METHODS = "android.software.input_methods"; + field public static final java.lang.String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels"; field public static final java.lang.String FEATURE_IRIS = "android.hardware.iris"; field public static final java.lang.String FEATURE_LEANBACK = "android.software.leanback"; field public static final java.lang.String FEATURE_LEANBACK_ONLY = "android.software.leanback_only"; @@ -11724,12 +11801,20 @@ package android.content.pm { ctor public ServiceInfo(android.content.pm.ServiceInfo); method public int describeContents(); method public void dump(android.util.Printer, java.lang.String); + method public int getForegroundServiceType(); field public static final android.os.Parcelable.Creator<android.content.pm.ServiceInfo> CREATOR; field public static final int FLAG_EXTERNAL_SERVICE = 4; // 0x4 field public static final int FLAG_ISOLATED_PROCESS = 2; // 0x2 field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000 field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1 field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8 + field public static final int FOREGROUND_SERVICE_TYPE_DEVICE_COMPANION = 5; // 0x5 + field public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 4; // 0x4 + field public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAY = 2; // 0x2 + field public static final int FOREGROUND_SERVICE_TYPE_ONGOING_PROCESS = 6; // 0x6 + field public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 3; // 0x3 + field public static final int FOREGROUND_SERVICE_TYPE_SYNC = 1; // 0x1 + field public static final int FOREGROUND_SERVICE_TYPE_UNSPECIFIED = 0; // 0x0 field public int flags; field public java.lang.String permission; } @@ -15681,6 +15766,7 @@ package android.graphics.text { public static class MeasuredText.Builder { ctor public MeasuredText.Builder(char[]); + ctor public MeasuredText.Builder(android.graphics.text.MeasuredText); method public android.graphics.text.MeasuredText.Builder appendReplacementRun(android.graphics.Paint, int, float); method public android.graphics.text.MeasuredText.Builder appendStyleRun(android.graphics.Paint, int, boolean); method public android.graphics.text.MeasuredText build(); @@ -22701,8 +22787,8 @@ package android.location { method public boolean addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler); method public void addProximityAlert(double, double, float, long, android.app.PendingIntent); method public void addTestProvider(java.lang.String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int); - method public void clearTestProviderEnabled(java.lang.String); - method public void clearTestProviderLocation(java.lang.String); + method public deprecated void clearTestProviderEnabled(java.lang.String); + method public deprecated void clearTestProviderLocation(java.lang.String); method public deprecated void clearTestProviderStatus(java.lang.String); method public java.util.List<java.lang.String> getAllProviders(); method public java.lang.String getBestProvider(android.location.Criteria, boolean); @@ -22914,6 +23000,7 @@ package android.media { method public int getChannelIndexMask(); method public int getChannelMask(); method public int getEncoding(); + method public int getFrameSizeInBytes(); method public int getSampleRate(); method public void writeToParcel(android.os.Parcel, int); field public static final deprecated int CHANNEL_CONFIGURATION_DEFAULT = 1; // 0x1 @@ -23016,6 +23103,7 @@ package android.media { method public deprecated boolean isBluetoothA2dpOn(); method public boolean isBluetoothScoAvailableOffCall(); method public boolean isBluetoothScoOn(); + method public static boolean isHapticPlaybackSupported(); method public boolean isMicrophoneMute(); method public boolean isMusicActive(); method public static boolean isOffloadedPlaybackSupported(android.media.AudioFormat); @@ -23353,6 +23441,7 @@ package android.media { method public int getStreamType(); method public boolean getTimestamp(android.media.AudioTimestamp); method public int getUnderrunCount(); + method public static boolean isDirectPlaybackSupported(android.media.AudioFormat, android.media.AudioAttributes); method public void pause() throws java.lang.IllegalStateException; method public void play() throws java.lang.IllegalStateException; method public void registerStreamEventCallback(java.util.concurrent.Executor, android.media.AudioTrack.StreamEventCallback); @@ -23911,6 +24000,7 @@ package android.media { method public void releaseOutputBuffer(int, boolean); method public void releaseOutputBuffer(int, long); method public void reset(); + method public void setAudioPresentation(android.media.AudioPresentation); method public void setCallback(android.media.MediaCodec.Callback, android.os.Handler); method public void setCallback(android.media.MediaCodec.Callback); method public void setInputSurface(android.view.Surface); @@ -23933,6 +24023,7 @@ package android.media { field public static final deprecated int INFO_OUTPUT_BUFFERS_CHANGED = -3; // 0xfffffffd field public static final int INFO_OUTPUT_FORMAT_CHANGED = -2; // 0xfffffffe field public static final int INFO_TRY_AGAIN_LATER = -1; // 0xffffffff + field public static final java.lang.String PARAMETER_KEY_HDR10_PLUS_INFO = "hdr10-plus-info"; field public static final java.lang.String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync"; field public static final java.lang.String PARAMETER_KEY_SUSPEND = "drop-input-frames"; field public static final java.lang.String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate"; @@ -24206,6 +24297,7 @@ package android.media { field public static final int HEVCProfileMain = 1; // 0x1 field public static final int HEVCProfileMain10 = 2; // 0x2 field public static final int HEVCProfileMain10HDR10 = 4096; // 0x1000 + field public static final int HEVCProfileMain10HDR10Plus = 8192; // 0x2000 field public static final int HEVCProfileMainStill = 4; // 0x4 field public static final int MPEG2LevelH14 = 2; // 0x2 field public static final int MPEG2LevelHL = 3; // 0x3 @@ -24267,8 +24359,10 @@ package android.media { field public static final int VP9Profile1 = 2; // 0x2 field public static final int VP9Profile2 = 4; // 0x4 field public static final int VP9Profile2HDR = 4096; // 0x1000 + field public static final int VP9Profile2HDR10Plus = 16384; // 0x4000 field public static final int VP9Profile3 = 8; // 0x8 field public static final int VP9Profile3HDR = 8192; // 0x2000 + field public static final int VP9Profile3HDR10Plus = 32768; // 0x8000 field public int level; field public int profile; } @@ -24667,6 +24761,7 @@ package android.media { field public static final java.lang.String KEY_FRAME_RATE = "frame-rate"; field public static final java.lang.String KEY_GRID_COLUMNS = "grid-cols"; field public static final java.lang.String KEY_GRID_ROWS = "grid-rows"; + field public static final java.lang.String KEY_HDR10_PLUS_INFO = "hdr10-plus-info"; field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info"; field public static final java.lang.String KEY_HEIGHT = "height"; field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period"; @@ -24705,6 +24800,7 @@ package android.media { field public static final java.lang.String MIMETYPE_AUDIO_AMR_NB = "audio/3gpp"; field public static final java.lang.String MIMETYPE_AUDIO_AMR_WB = "audio/amr-wb"; field public static final java.lang.String MIMETYPE_AUDIO_EAC3 = "audio/eac3"; + field public static final java.lang.String MIMETYPE_AUDIO_EAC3_JOC = "audio/eac3-joc"; field public static final java.lang.String MIMETYPE_AUDIO_FLAC = "audio/flac"; field public static final java.lang.String MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw"; field public static final java.lang.String MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw"; @@ -24895,6 +24991,7 @@ package android.media { field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2 field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3 field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0 + field public static final int MUXER_OUTPUT_OGG = 4; // 0x4 field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1 } @@ -25350,6 +25447,7 @@ package android.media { field public static final int AMR_WB = 2; // 0x2 field public static final int DEFAULT = 0; // 0x0 field public static final int HE_AAC = 4; // 0x4 + field public static final int OPUS = 7; // 0x7 field public static final int VORBIS = 6; // 0x6 } @@ -25400,6 +25498,7 @@ package android.media { field public static final int DEFAULT = 0; // 0x0 field public static final int MPEG_2_TS = 8; // 0x8 field public static final int MPEG_4 = 2; // 0x2 + field public static final int OGG = 11; // 0xb field public static final deprecated int RAW_AMR = 3; // 0x3 field public static final int THREE_GPP = 1; // 0x1 field public static final int WEBM = 9; // 0x9 @@ -27019,6 +27118,7 @@ package android.media.session { public static final class MediaSessionManager.RemoteUserInfo { ctor public MediaSessionManager.RemoteUserInfo(java.lang.String, int, int); + ctor public MediaSessionManager.RemoteUserInfo(java.lang.String, int, int, android.os.IBinder); method public java.lang.String getPackageName(); method public int getPid(); method public int getUid(); @@ -28339,6 +28439,7 @@ package android.net { method public int describeContents(); method public int getLinkDownstreamBandwidthKbps(); method public int getLinkUpstreamBandwidthKbps(); + method public android.net.TransportInfo getTransportInfo(); method public boolean hasCapability(int); method public boolean hasTransport(int); method public void writeToParcel(android.os.Parcel, int); @@ -28547,6 +28648,9 @@ package android.net { field public static final int UNSUPPORTED = -1; // 0xffffffff } + public abstract interface TransportInfo { + } + public abstract class Uri implements java.lang.Comparable android.os.Parcelable { method public abstract android.net.Uri.Builder buildUpon(); method public int compareTo(android.net.Uri); @@ -28582,6 +28686,7 @@ package android.net { method public abstract boolean isRelative(); method public android.net.Uri normalizeScheme(); method public static android.net.Uri parse(java.lang.String); + method public java.lang.String toSafeString(); method public abstract java.lang.String toString(); method public static android.net.Uri withAppendedPath(android.net.Uri, java.lang.String); method public static void writeToParcel(android.os.Parcel, android.net.Uri); @@ -34659,6 +34764,7 @@ package android.os { field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering"; field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn"; field public static final java.lang.String DISALLOW_CONFIG_WIFI = "no_config_wifi"; + field public static final java.lang.String DISALLOW_CONTENT_CAPTURE = "no_content_capture"; field public static final java.lang.String DISALLOW_CREATE_WINDOWS = "no_create_windows"; field public static final java.lang.String DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste"; field public static final java.lang.String DISALLOW_DATA_ROAMING = "no_data_roaming"; @@ -34668,7 +34774,6 @@ package android.os { field public static final java.lang.String DISALLOW_INSTALL_APPS = "no_install_apps"; field public static final java.lang.String DISALLOW_INSTALL_UNKNOWN_SOURCES = "no_install_unknown_sources"; field public static final java.lang.String DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY = "no_install_unknown_sources_globally"; - field public static final java.lang.String DISALLOW_INTELLIGENCE_CAPTURE = "no_intelligence_capture"; field public static final java.lang.String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts"; field public static final java.lang.String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media"; field public static final java.lang.String DISALLOW_NETWORK_RESET = "no_network_reset"; @@ -35872,9 +35977,11 @@ package android.provider { } public final class CalendarContract { + method public static boolean startViewCalendarEventInManagedProfile(android.content.Context, long, long, long, boolean, int); field public static final java.lang.String ACCOUNT_TYPE_LOCAL = "LOCAL"; field public static final java.lang.String ACTION_EVENT_REMINDER = "android.intent.action.EVENT_REMINDER"; field public static final java.lang.String ACTION_HANDLE_CUSTOM_EVENT = "android.provider.calendar.action.HANDLE_CUSTOM_EVENT"; + field public static final java.lang.String ACTION_VIEW_WORK_CALENDAR_EVENT = "android.provider.calendar.action.VIEW_WORK_CALENDAR_EVENT"; field public static final java.lang.String AUTHORITY = "com.android.calendar"; field public static final java.lang.String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter"; field public static final android.net.Uri CONTENT_URI; @@ -35882,6 +35989,7 @@ package android.provider { field public static final java.lang.String EXTRA_EVENT_ALL_DAY = "allDay"; field public static final java.lang.String EXTRA_EVENT_BEGIN_TIME = "beginTime"; field public static final java.lang.String EXTRA_EVENT_END_TIME = "endTime"; + field public static final java.lang.String EXTRA_EVENT_ID = "id"; } public static final class CalendarContract.Attendees implements android.provider.BaseColumns android.provider.CalendarContract.AttendeesColumns android.provider.CalendarContract.EventsColumns { @@ -35998,6 +36106,7 @@ package android.provider { field public static final java.lang.String CALENDAR_LOCATION = "calendar_location"; field public static final android.net.Uri CONTENT_URI; field public static final java.lang.String DEFAULT_SORT_ORDER = "calendar_displayName"; + field public static final android.net.Uri ENTERPRISE_CONTENT_URI; field public static final java.lang.String NAME = "name"; } @@ -36026,6 +36135,7 @@ package android.provider { public static final class CalendarContract.Events implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.EventsColumns android.provider.CalendarContract.SyncColumns { field public static final android.net.Uri CONTENT_EXCEPTION_URI; field public static final android.net.Uri CONTENT_URI; + field public static final android.net.Uri ENTERPRISE_CONTENT_URI; } protected static abstract interface CalendarContract.EventsColumns { @@ -36117,6 +36227,10 @@ package android.provider { field public static final java.lang.String END = "end"; field public static final java.lang.String END_DAY = "endDay"; field public static final java.lang.String END_MINUTE = "endMinute"; + field public static final android.net.Uri ENTERPRISE_CONTENT_BY_DAY_URI; + field public static final android.net.Uri ENTERPRISE_CONTENT_SEARCH_BY_DAY_URI; + field public static final android.net.Uri ENTERPRISE_CONTENT_SEARCH_URI; + field public static final android.net.Uri ENTERPRISE_CONTENT_URI; field public static final java.lang.String EVENT_ID = "event_id"; field public static final java.lang.String START_DAY = "startDay"; field public static final java.lang.String START_MINUTE = "startMinute"; @@ -37362,22 +37476,26 @@ package android.provider { method public static android.net.Uri buildRootsUri(java.lang.String); method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String); method public static android.net.Uri buildTreeDocumentUri(java.lang.String, java.lang.String); - method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; - method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; - method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException; - method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; - method public static void ejectRoot(android.content.ContentResolver, android.net.Uri); - method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.net.Uri copyDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.net.Uri createDocument(android.content.ContentInterface, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public static android.content.IntentSender createWebLinkIntent(android.content.ContentInterface, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException; + method public static boolean deleteDocument(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException; + method public static void ejectRoot(android.content.ContentInterface, android.net.Uri); + method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException; method public static java.lang.String getDocumentId(android.net.Uri); - method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException; + method public static android.os.Bundle getDocumentMetadata(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentInterface, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public static java.lang.String getRootId(android.net.Uri); method public static java.lang.String getSearchDocumentsQuery(android.net.Uri); method public static java.lang.String getTreeDocumentId(android.net.Uri); + method public static boolean isChildDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; method public static boolean isDocumentUri(android.content.Context, android.net.Uri); + method public static boolean isRootUri(android.content.Context, android.net.Uri); + method public static boolean isRootsUri(android.content.Context, android.net.Uri); method public static boolean isTreeUri(android.net.Uri); - method public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; - method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; - method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; + method public static android.net.Uri moveDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static boolean removeDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.net.Uri renameDocument(android.content.ContentInterface, android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; field public static final java.lang.String ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS"; field public static final java.lang.String EXTRA_ERROR = "error"; field public static final java.lang.String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF"; @@ -37386,7 +37504,16 @@ package android.provider { field public static final java.lang.String EXTRA_LOADING = "loading"; field public static final java.lang.String EXTRA_ORIENTATION = "android.provider.extra.ORIENTATION"; field public static final java.lang.String EXTRA_PROMPT = "android.provider.extra.PROMPT"; + field public static final java.lang.String METADATA_EXIF = "android:documentExif"; + field public static final java.lang.String METADATA_TREE_COUNT = "android:metadataTreeCount"; + field public static final java.lang.String METADATA_TREE_SIZE = "android:metadataTreeSize"; + field public static final java.lang.String METADATA_TYPES = "android:documentMetadataTypes"; field public static final java.lang.String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER"; + field public static final java.lang.String QUERY_ARG_DISPLAY_NAME = "android:query-arg-display-name"; + field public static final java.lang.String QUERY_ARG_EXCLUDE_MEDIA = "android:query-arg-exclude-media"; + field public static final java.lang.String QUERY_ARG_FILE_SIZE_OVER = "android:query-arg-file-size-over"; + field public static final java.lang.String QUERY_ARG_LAST_MODIFIED_AFTER = "android:query-arg-last-modified-after"; + field public static final java.lang.String QUERY_ARG_MIME_TYPES = "android:query-arg-mime-types"; } public static final class DocumentsContract.Document { @@ -37401,8 +37528,10 @@ package android.provider { field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10 field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20 field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 + field public static final int FLAG_PARTIAL = 8192; // 0x2000 field public static final int FLAG_SUPPORTS_COPY = 128; // 0x80 field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 + field public static final int FLAG_SUPPORTS_METADATA = 16384; // 0x4000 field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100 field public static final int FLAG_SUPPORTS_REMOVE = 1024; // 0x400 field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40 @@ -37433,6 +37562,7 @@ package android.provider { field public static final java.lang.String COLUMN_ROOT_ID = "root_id"; field public static final java.lang.String COLUMN_SUMMARY = "summary"; field public static final java.lang.String COLUMN_TITLE = "title"; + field public static final int FLAG_EMPTY = 64; // 0x40 field public static final int FLAG_LOCAL_ONLY = 2; // 0x2 field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1 field public static final int FLAG_SUPPORTS_EJECT = 32; // 0x20 @@ -37451,6 +37581,7 @@ package android.provider { method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException; method public void ejectRoot(java.lang.String); method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public android.os.Bundle getDocumentMetadata(java.lang.String) throws java.io.FileNotFoundException; method public java.lang.String[] getDocumentStreamTypes(java.lang.String, java.lang.String); method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException; method public final java.lang.String getType(android.net.Uri); @@ -37475,6 +37606,7 @@ package android.provider { method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[], android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException; method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; + method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException; method public void removeDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public java.lang.String renameDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public final void revokeDocumentPermission(java.lang.String); @@ -37751,6 +37883,7 @@ package android.provider { public static final class MediaStore.Downloads implements android.provider.MediaStore.DownloadColumns { method public static android.net.Uri getContentUri(java.lang.String); + field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/download"; field public static final android.net.Uri EXTERNAL_CONTENT_URI; field public static final android.net.Uri INTERNAL_CONTENT_URI; } @@ -38387,10 +38520,13 @@ package android.provider { } public static final class Telephony.CarrierId implements android.provider.BaseColumns { + method public static android.net.Uri getPreciseCarrierIdUriForSubscriptionId(int); method public static android.net.Uri getUriForSubscriptionId(int); field public static final java.lang.String CARRIER_ID = "carrier_id"; field public static final java.lang.String CARRIER_NAME = "carrier_name"; field public static final android.net.Uri CONTENT_URI; + field public static final java.lang.String PRECISE_CARRIER_ID = "precise_carrier_id"; + field public static final java.lang.String PRECISE_CARRIER_ID_NAME = "precise_carrier_id_name"; } public static final class Telephony.Carriers implements android.provider.BaseColumns { @@ -40394,6 +40530,7 @@ package android.service.autofill { method public android.service.autofill.FillResponse.Builder setHeader(android.widget.RemoteViews); method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...); method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo); + method public android.service.autofill.FillResponse.Builder setUserData(android.service.autofill.UserData); } public final class ImageTransformation implements android.os.Parcelable android.service.autofill.Transformation { @@ -40489,6 +40626,7 @@ package android.service.autofill { public final class UserData implements android.os.Parcelable { method public int describeContents(); method public java.lang.String getFieldClassificationAlgorithm(); + method public java.lang.String getFieldClassificationAlgorithmForCategory(java.lang.String); method public java.lang.String getId(); method public static int getMaxCategoryCount(); method public static int getMaxFieldClassificationIdsSize(); @@ -40504,6 +40642,7 @@ package android.service.autofill { method public android.service.autofill.UserData.Builder add(java.lang.String, java.lang.String); method public android.service.autofill.UserData build(); method public android.service.autofill.UserData.Builder setFieldClassificationAlgorithm(java.lang.String, android.os.Bundle); + method public android.service.autofill.UserData.Builder setFieldClassificationAlgorithmForCategory(java.lang.String, java.lang.String, android.os.Bundle); } public abstract interface Validator { @@ -40533,13 +40672,16 @@ package android.service.carrier { public class CarrierIdentifier implements android.os.Parcelable { ctor public CarrierIdentifier(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String); + ctor public CarrierIdentifier(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, int, int); ctor public CarrierIdentifier(byte[], java.lang.String, java.lang.String); method public int describeContents(); + method public int getCarrierId(); method public java.lang.String getGid1(); method public java.lang.String getGid2(); method public java.lang.String getImsi(); method public java.lang.String getMcc(); method public java.lang.String getMnc(); + method public int getPreciseCarrierId(); method public java.lang.String getSpn(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.service.carrier.CarrierIdentifier> CREATOR; @@ -42253,8 +42395,9 @@ package android.telecom { method public void swapConference(); method public void unhold(); method public void unregisterCallback(android.telecom.Call.Callback); - field public static final java.lang.String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts"; + field public static final deprecated java.lang.String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts"; field public static final java.lang.String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS = "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS"; + field public static final java.lang.String EXTRA_SUGGESTED_PHONE_ACCOUNTS = "android.telecom.extra.SUGGESTED_PHONE_ACCOUNTS"; field public static final int STATE_ACTIVE = 4; // 0x4 field public static final int STATE_CONNECTING = 9; // 0x9 field public static final int STATE_DIALING = 1; // 0x1 @@ -42825,6 +42968,20 @@ package android.telecom { field public static final android.os.Parcelable.Creator<android.telecom.PhoneAccountHandle> CREATOR; } + public final class PhoneAccountSuggestion implements android.os.Parcelable { + method public int describeContents(); + method public android.telecom.PhoneAccountHandle getPhoneAccountHandle(); + method public int getReason(); + method public boolean shouldAutoSelect(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telecom.PhoneAccountSuggestion> CREATOR; + field public static final int REASON_FREQUENT = 2; // 0x2 + field public static final int REASON_INTRA_CARRIER = 1; // 0x1 + field public static final int REASON_NONE = 0; // 0x0 + field public static final int REASON_OTHER = 4; // 0x4 + field public static final int REASON_USER_SET = 3; // 0x3 + } + public final class RemoteConference { method public void disconnect(); method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections(); @@ -43016,12 +43173,14 @@ package android.telecom { field public static final java.lang.String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT"; field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE"; field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE"; + field public static final java.lang.String EXTRA_IS_ENABLED = "android.telecom.extra.IS_ENABLED"; field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS"; field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE"; field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS"; field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS"; field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING"; field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI"; + field public static final java.lang.String METADATA_IN_CALL_SERVICE_CAR_MODE_UI = "android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"; field public static final int PRESENTATION_ALLOWED = 1; // 0x1 field public static final int PRESENTATION_PAYPHONE = 4; // 0x4 field public static final int PRESENTATION_RESTRICTED = 2; // 0x2 @@ -43209,6 +43368,7 @@ package android.telephony { field public static final java.lang.String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array"; field public static final java.lang.String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool"; field public static final java.lang.String KEY_CARRIER_CALL_SCREENING_APP_STRING = "call_screening_app"; + field public static final java.lang.String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string"; field public static final java.lang.String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings"; field public static final java.lang.String KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT = "carrier_default_wfc_ims_mode_int"; field public static final java.lang.String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT = "carrier_default_wfc_ims_roaming_mode_int"; @@ -43712,10 +43872,10 @@ package android.telephony { method public static java.lang.String getStrippedReversed(java.lang.String); method public static final boolean is12Key(char); method public static final boolean isDialable(char); - method public static boolean isEmergencyNumber(java.lang.String); + method public static deprecated boolean isEmergencyNumber(java.lang.String); method public static boolean isGlobalPhoneNumber(java.lang.String); method public static boolean isISODigit(char); - method public static boolean isLocalEmergencyNumber(android.content.Context, java.lang.String); + method public static deprecated boolean isLocalEmergencyNumber(android.content.Context, java.lang.String); method public static final boolean isNonSeparator(char); method public static final boolean isReallyDialable(char); method public static final boolean isStartsPostDial(char); @@ -44006,12 +44166,14 @@ package android.telephony { method public static int getSlotIndex(int); method public int[] getSubscriptionIds(int); method public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int); + method public java.util.List<android.telephony.SubscriptionInfo> getSubscriptionsInGroup(int); method public boolean isActiveSubscriptionId(int); method public boolean isNetworkRoaming(int); method public static boolean isUsableSubscriptionId(int); method public static boolean isValidSubscriptionId(int); method public void removeOnOpportunisticSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener); method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); + method public boolean removeSubscriptionsFromGroup(int[]); method public java.lang.String setSubscriptionGroup(int[]); method public void setSubscriptionOverrideCongested(int, boolean, long); method public void setSubscriptionOverrideUnmetered(int, boolean, long); @@ -44076,6 +44238,7 @@ package android.telephony { method public java.util.List<android.telephony.CellInfo> getAllCellInfo(); method public int getCallState(); method public android.os.PersistableBundle getCarrierConfig(); + method public int getCarrierIdFromSimMccMnc(); method public deprecated android.telephony.CellLocation getCellLocation(); method public java.util.Map<java.lang.Integer, java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(); method public java.util.Map<java.lang.Integer, java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(int); @@ -44113,6 +44276,8 @@ package android.telephony { method public java.lang.String getSimCountryIso(); method public java.lang.String getSimOperator(); method public java.lang.String getSimOperatorName(); + method public int getSimPreciseCarrierId(); + method public java.lang.CharSequence getSimPreciseCarrierIdName(); method public java.lang.String getSimSerialNumber(); method public int getSimState(); method public int getSimState(int); @@ -44169,6 +44334,7 @@ package android.telephony { field public static final java.lang.String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION"; field public static final java.lang.String ACTION_SMS_APP_SERVICE = "android.telephony.action.SMS_APP_SERVICE"; field public static final java.lang.String ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED"; + field public static final java.lang.String ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED"; field public static final int APPTYPE_CSIM = 4; // 0x4 field public static final int APPTYPE_ISIM = 5; // 0x5 field public static final int APPTYPE_RUIM = 3; // 0x3 @@ -44201,6 +44367,8 @@ package android.telephony { field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT"; field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT"; field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telephony.extra.PHONE_ACCOUNT_HANDLE"; + field public static final java.lang.String EXTRA_PRECISE_CARRIER_ID = "android.telephony.extra.PRECISE_CARRIER_ID"; + field public static final java.lang.String EXTRA_PRECISE_CARRIER_NAME = "android.telephony.extra.PRECISE_CARRIER_NAME"; field public static final java.lang.String EXTRA_STATE = "state"; field public static final java.lang.String EXTRA_STATE_IDLE; field public static final java.lang.String EXTRA_STATE_OFFHOOK; @@ -44423,11 +44591,13 @@ package android.telephony.emergency { method public java.util.List<java.lang.Integer> getEmergencyNumberSources(); method public java.util.List<java.lang.Integer> getEmergencyServiceCategories(); method public int getEmergencyServiceCategoryBitmask(); + method public java.lang.String getMnc(); method public java.lang.String getNumber(); method public boolean isFromSources(int); method public boolean isInEmergencyServiceCategories(int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.emergency.EmergencyNumber> CREATOR; + field public static final int EMERGENCY_NUMBER_SOURCE_DATABASE = 16; // 0x10 field public static final int EMERGENCY_NUMBER_SOURCE_DEFAULT = 8; // 0x8 field public static final int EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG = 4; // 0x4 field public static final int EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING = 1; // 0x1 @@ -45161,6 +45331,7 @@ package android.text { public static class PrecomputedText.Params.Builder { ctor public PrecomputedText.Params.Builder(android.text.TextPaint); + ctor public PrecomputedText.Params.Builder(android.text.PrecomputedText.Params); method public android.text.PrecomputedText.Params build(); method public android.text.PrecomputedText.Params.Builder setBreakStrategy(int); method public android.text.PrecomputedText.Params.Builder setHyphenationFrequency(int); @@ -51850,6 +52021,21 @@ package android.view.autofill { } +package android.view.contentcapture { + + public final class ContentCaptureManager { + method public android.content.ComponentName getServiceComponentName(); + method public boolean isContentCaptureEnabled(); + method public android.view.ViewStructure newVirtualViewStructure(android.view.autofill.AutofillId, int); + method public void notifyViewAppeared(android.view.ViewStructure); + method public void notifyViewDisappeared(android.view.autofill.AutofillId); + method public void notifyViewTextChanged(android.view.autofill.AutofillId, java.lang.CharSequence, int); + method public void setContentCaptureEnabled(boolean); + field public static final int FLAG_USER_INPUT = 1; // 0x1 + } + +} + package android.view.inputmethod { public class BaseInputConnection implements android.view.inputmethod.InputConnection { @@ -52331,21 +52517,6 @@ package android.view.inspector { } -package android.view.intelligence { - - public final class ContentCaptureManager { - method public android.content.ComponentName getServiceComponentName(); - method public boolean isContentCaptureEnabled(); - method public android.view.ViewStructure newVirtualViewStructure(android.view.autofill.AutofillId, int); - method public void notifyViewAppeared(android.view.ViewStructure); - method public void notifyViewDisappeared(android.view.autofill.AutofillId); - method public void notifyViewTextChanged(android.view.autofill.AutofillId, java.lang.CharSequence, int); - method public void setContentCaptureEnabled(boolean); - field public static final int FLAG_USER_INPUT = 1; // 0x1 - } - -} - package android.view.textclassifier { public final class ConversationActions implements android.os.Parcelable { @@ -52392,24 +52563,25 @@ package android.view.textclassifier { method public int describeContents(); method public android.app.Person getAuthor(); method public android.os.Bundle getExtras(); + method public java.time.ZonedDateTime getReferenceTime(); method public java.lang.CharSequence getText(); - method public java.time.ZonedDateTime getTime(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR; field public static final android.app.Person PERSON_USER_LOCAL; + field public static final android.app.Person PERSON_USER_REMOTE; } public static final class ConversationActions.Message.Builder { - ctor public ConversationActions.Message.Builder(); + ctor public ConversationActions.Message.Builder(android.app.Person); method public android.view.textclassifier.ConversationActions.Message build(); - method public android.view.textclassifier.ConversationActions.Message.Builder setAuthor(android.app.Person); - method public android.view.textclassifier.ConversationActions.Message.Builder setComposeTime(java.time.ZonedDateTime); method public android.view.textclassifier.ConversationActions.Message.Builder setExtras(android.os.Bundle); + method public android.view.textclassifier.ConversationActions.Message.Builder setReferenceTime(java.time.ZonedDateTime); method public android.view.textclassifier.ConversationActions.Message.Builder setText(java.lang.CharSequence); } public static final class ConversationActions.Request implements android.os.Parcelable { method public int describeContents(); + method public java.lang.String getCallingPackageName(); method public java.util.List<android.view.textclassifier.ConversationActions.Message> getConversation(); method public java.util.List<java.lang.String> getHints(); method public int getMaxSuggestions(); @@ -52523,6 +52695,7 @@ package android.view.textclassifier { public static final class TextClassification.Request implements android.os.Parcelable { method public int describeContents(); + method public java.lang.String getCallingPackageName(); method public android.os.LocaleList getDefaultLocales(); method public int getEndIndex(); method public android.os.Bundle getExtras(); @@ -52640,6 +52813,7 @@ package android.view.textclassifier { public static final class TextLanguage.Request implements android.os.Parcelable { method public int describeContents(); + method public java.lang.String getCallingPackageName(); method public android.os.Bundle getExtras(); method public java.lang.CharSequence getText(); method public void writeToParcel(android.os.Parcel, int); @@ -52671,6 +52845,7 @@ package android.view.textclassifier { public static final class TextLinks.Builder { ctor public TextLinks.Builder(java.lang.String); method public android.view.textclassifier.TextLinks.Builder addLink(int, int, java.util.Map<java.lang.String, java.lang.Float>); + method public android.view.textclassifier.TextLinks.Builder addLink(int, int, java.util.Map<java.lang.String, java.lang.Float>, android.os.Bundle); method public android.view.textclassifier.TextLinks build(); method public android.view.textclassifier.TextLinks.Builder clearTextLinks(); method public android.view.textclassifier.TextLinks.Builder setExtras(android.os.Bundle); @@ -52678,6 +52853,7 @@ package android.view.textclassifier { public static final class TextLinks.Request implements android.os.Parcelable { method public int describeContents(); + method public java.lang.String getCallingPackageName(); method public android.os.LocaleList getDefaultLocales(); method public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig(); method public android.os.Bundle getExtras(); @@ -52700,6 +52876,7 @@ package android.view.textclassifier { method public int getEnd(); method public java.lang.String getEntity(int); method public int getEntityCount(); + method public android.os.Bundle getExtras(); method public int getStart(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks.TextLink> CREATOR; @@ -52734,6 +52911,7 @@ package android.view.textclassifier { public static final class TextSelection.Request implements android.os.Parcelable { method public int describeContents(); + method public java.lang.String getCallingPackageName(); method public android.os.LocaleList getDefaultLocales(); method public int getEndIndex(); method public android.os.Bundle getExtras(); @@ -53234,7 +53412,7 @@ package android.webkit { method public abstract boolean getDomStorageEnabled(); method public abstract java.lang.String getFantasyFontFamily(); method public abstract java.lang.String getFixedFontFamily(); - method public abstract int getForceDarkMode(); + method public int getForceDarkMode(); method public abstract boolean getJavaScriptCanOpenWindowsAutomatically(); method public abstract boolean getJavaScriptEnabled(); method public abstract android.webkit.WebSettings.LayoutAlgorithm getLayoutAlgorithm(); @@ -53281,7 +53459,7 @@ package android.webkit { method public abstract deprecated void setEnableSmoothTransition(boolean); method public abstract void setFantasyFontFamily(java.lang.String); method public abstract void setFixedFontFamily(java.lang.String); - method public abstract void setForceDarkMode(int); + method public void setForceDarkMode(int); method public abstract deprecated void setGeolocationDatabasePath(java.lang.String); method public abstract void setGeolocationEnabled(boolean); method public abstract void setJavaScriptCanOpenWindowsAutomatically(boolean); diff --git a/api/removed.txt b/api/removed.txt index f7106d2207ec..e3e8b6397e6f 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -508,6 +508,21 @@ package android.provider { field public static final deprecated java.lang.String TIMESTAMP = "timestamp"; } + public final class DocumentsContract { + method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException; + method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; + method public static void ejectRoot(android.content.ContentResolver, android.net.Uri); + method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.os.Bundle getDocumentMetadata(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException; + method public static boolean isChildDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; + } + public static final class Settings.Global extends android.provider.Settings.NameValueTable { field public static final deprecated java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync"; } diff --git a/api/system-current.txt b/api/system-current.txt index 9cf66b2707fd..09dca1e1b298 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4,24 +4,23 @@ package android { field public static final java.lang.String ACCESS_AMBIENT_LIGHT_STATS = "android.permission.ACCESS_AMBIENT_LIGHT_STATS"; field public static final java.lang.String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO"; field public static final java.lang.String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM"; - field public static final java.lang.String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES"; field public static final java.lang.String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES"; field public static final deprecated java.lang.String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO"; + field public static final java.lang.String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS"; field public static final java.lang.String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION"; field public static final java.lang.String ACCESS_MTP = "android.permission.ACCESS_MTP"; field public static final java.lang.String ACCESS_NETWORK_CONDITIONS = "android.permission.ACCESS_NETWORK_CONDITIONS"; field public static final java.lang.String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; field public static final java.lang.String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS"; field public static final java.lang.String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER"; - field public static final java.lang.String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER"; field public static final java.lang.String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final java.lang.String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE"; field public static final java.lang.String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK"; field public static final java.lang.String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER"; field public static final java.lang.String BACKUP = "android.permission.BACKUP"; - field public static final java.lang.String BATTERY_STATS = "android.permission.BATTERY_STATS"; - field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET"; + field public static final java.lang.String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"; field public static final deprecated java.lang.String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE"; + field public static final java.lang.String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE"; field public static final java.lang.String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH"; field public static final java.lang.String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE"; field public static final java.lang.String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE"; @@ -29,29 +28,22 @@ package android { field public static final java.lang.String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"; field public static final java.lang.String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE"; field public static final java.lang.String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE"; - field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS"; field public static final java.lang.String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE"; field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE"; field public static final java.lang.String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"; - field public static final java.lang.String BIND_SMART_SUGGESTIONS_SERVICE = "android.permission.BIND_SMART_SUGGESTIONS_SERVICE"; field public static final java.lang.String BIND_SOUND_TRIGGER_DETECTION_SERVICE = "android.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE"; field public static final java.lang.String BIND_TELEPHONY_DATA_SERVICE = "android.permission.BIND_TELEPHONY_DATA_SERVICE"; field public static final java.lang.String BIND_TELEPHONY_NETWORK_SERVICE = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE"; field public static final java.lang.String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE"; field public static final java.lang.String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT"; field public static final java.lang.String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE"; - field public static final java.lang.String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED"; field public static final java.lang.String BRICK = "android.permission.BRICK"; field public static final java.lang.String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final deprecated java.lang.String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED"; - field public static final java.lang.String CALL_PRIVILEGED = "android.permission.CALL_PRIVILEGED"; field public static final java.lang.String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED"; field public static final java.lang.String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD"; - field public static final java.lang.String CAPTURE_AUDIO_OUTPUT = "android.permission.CAPTURE_AUDIO_OUTPUT"; field public static final java.lang.String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT"; field public static final java.lang.String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; - field public static final java.lang.String CHANGE_COMPONENT_ENABLED_STATE = "android.permission.CHANGE_COMPONENT_ENABLED_STATE"; - field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION"; field public static final java.lang.String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"; field public static final java.lang.String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; @@ -61,22 +53,15 @@ package android { field public static final java.lang.String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION"; field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE"; field public static final java.lang.String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"; - field public static final java.lang.String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES"; field public static final java.lang.String CONTROL_VPN = "android.permission.CONTROL_VPN"; field public static final java.lang.String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER"; - field public static final java.lang.String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES"; - field public static final java.lang.String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES"; field public static final java.lang.String DEVICE_POWER = "android.permission.DEVICE_POWER"; - field public static final java.lang.String DIAGNOSTIC = "android.permission.DIAGNOSTIC"; field public static final java.lang.String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE"; - field public static final java.lang.String DUMP = "android.permission.DUMP"; field public static final java.lang.String FORCE_BACK = "android.permission.FORCE_BACK"; field public static final java.lang.String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; - field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED"; field public static final java.lang.String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS"; field public static final java.lang.String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"; field public static final java.lang.String GET_TOP_ACTIVITY_INFO = "android.permission.GET_TOP_ACTIVITY_INFO"; - field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH"; field public static final java.lang.String GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS = "android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS"; field public static final java.lang.String GRANT_RUNTIME_PERMISSIONS = "android.permission.GRANT_RUNTIME_PERMISSIONS"; field public static final java.lang.String HARDWARE_TEST = "android.permission.HARDWARE_TEST"; @@ -84,18 +69,16 @@ package android { field public static final java.lang.String HIDE_NON_SYSTEM_OVERLAY_WINDOWS = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"; field public static final java.lang.String INJECT_EVENTS = "android.permission.INJECT_EVENTS"; field public static final java.lang.String INSTALL_GRANT_RUNTIME_PERMISSIONS = "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"; - field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER"; - field public static final java.lang.String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES"; field public static final java.lang.String INSTALL_PACKAGE_UPDATES = "android.permission.INSTALL_PACKAGE_UPDATES"; field public static final java.lang.String INSTALL_SELF_UPDATES = "android.permission.INSTALL_SELF_UPDATES"; field public static final java.lang.String INTENT_FILTER_VERIFICATION_AGENT = "android.permission.INTENT_FILTER_VERIFICATION_AGENT"; + field public static final java.lang.String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES"; field public static final java.lang.String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS"; field public static final java.lang.String INTERACT_ACROSS_USERS_FULL = "android.permission.INTERACT_ACROSS_USERS_FULL"; field public static final java.lang.String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW"; field public static final java.lang.String INVOKE_CARRIER_SETUP = "android.permission.INVOKE_CARRIER_SETUP"; field public static final java.lang.String KILL_UID = "android.permission.KILL_UID"; field public static final java.lang.String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS"; - field public static final java.lang.String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE"; field public static final java.lang.String LOCK_DEVICE = "android.permission.LOCK_DEVICE"; field public static final java.lang.String LOOP_RADIO = "android.permission.LOOP_RADIO"; field public static final java.lang.String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY"; @@ -105,36 +88,31 @@ package android { field public static final java.lang.String MANAGE_AUTO_FILL = "android.permission.MANAGE_AUTO_FILL"; field public static final java.lang.String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE"; field public static final java.lang.String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES"; + field public static final java.lang.String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE"; field public static final java.lang.String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING"; field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS"; field public static final java.lang.String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS"; - field public static final java.lang.String MANAGE_SMART_SUGGESTIONS = "android.permission.MANAGE_SMART_SUGGESTIONS"; field public static final java.lang.String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER"; field public static final java.lang.String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS"; field public static final java.lang.String MANAGE_USB = "android.permission.MANAGE_USB"; field public static final java.lang.String MANAGE_USERS = "android.permission.MANAGE_USERS"; field public static final java.lang.String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE"; - field public static final java.lang.String MASTER_CLEAR = "android.permission.MASTER_CLEAR"; - field public static final java.lang.String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL"; field public static final java.lang.String MODIFY_APPWIDGET_BIND_PERMISSIONS = "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"; field public static final java.lang.String MODIFY_AUDIO_ROUTING = "android.permission.MODIFY_AUDIO_ROUTING"; field public static final java.lang.String MODIFY_CELL_BROADCASTS = "android.permission.MODIFY_CELL_BROADCASTS"; field public static final java.lang.String MODIFY_DAY_NIGHT_MODE = "android.permission.MODIFY_DAY_NIGHT_MODE"; field public static final deprecated java.lang.String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING"; field public static final java.lang.String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS"; - field public static final java.lang.String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE"; field public static final java.lang.String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE"; - field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS"; - field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS"; field public static final java.lang.String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE"; field public static final java.lang.String NETWORK_MANAGED_PROVISIONING = "android.permission.NETWORK_MANAGED_PROVISIONING"; field public static final java.lang.String NETWORK_SETUP_WIZARD = "android.permission.NETWORK_SETUP_WIZARD"; field public static final java.lang.String NOTIFICATION_DURING_SETUP = "android.permission.NOTIFICATION_DURING_SETUP"; field public static final java.lang.String NOTIFY_TV_INPUTS = "android.permission.NOTIFY_TV_INPUTS"; field public static final java.lang.String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE"; + field public static final java.lang.String OBSERVE_ROLE_HOLDERS = "android.permission.OBSERVE_ROLE_HOLDERS"; field public static final java.lang.String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG"; - field public static final java.lang.String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS"; field public static final java.lang.String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT"; field public static final java.lang.String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS"; field public static final java.lang.String PERFORM_CDMA_PROVISIONING = "android.permission.PERFORM_CDMA_PROVISIONING"; @@ -147,7 +125,6 @@ package android { field public static final java.lang.String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS"; field public static final java.lang.String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE"; field public static final java.lang.String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS"; - field public static final java.lang.String READ_LOGS = "android.permission.READ_LOGS"; field public static final java.lang.String READ_NETWORK_USAGE_HISTORY = "android.permission.READ_NETWORK_USAGE_HISTORY"; field public static final java.lang.String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE"; field public static final java.lang.String READ_PRINT_SERVICES = "android.permission.READ_PRINT_SERVICES"; @@ -159,7 +136,6 @@ package android { field public static final java.lang.String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL"; field public static final java.lang.String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL"; field public static final java.lang.String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS"; - field public static final java.lang.String REBOOT = "android.permission.REBOOT"; field public static final java.lang.String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE"; field public static final java.lang.String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY"; field public static final java.lang.String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST"; @@ -170,33 +146,26 @@ package android { field public static final java.lang.String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER"; field public static final java.lang.String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION"; field public static final java.lang.String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES"; + field public static final java.lang.String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final java.lang.String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; field public static final java.lang.String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS"; field public static final java.lang.String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT"; field public static final java.lang.String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS"; field public static final java.lang.String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS"; field public static final java.lang.String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY"; - field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE"; field public static final java.lang.String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS"; field public static final java.lang.String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION"; field public static final java.lang.String SERIAL_PORT = "android.permission.SERIAL_PORT"; field public static final java.lang.String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER"; - field public static final java.lang.String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH"; - field public static final java.lang.String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE"; - field public static final java.lang.String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP"; field public static final java.lang.String SET_HARMFUL_APP_WARNINGS = "android.permission.SET_HARMFUL_APP_WARNINGS"; field public static final java.lang.String SET_MEDIA_KEY_LISTENER = "android.permission.SET_MEDIA_KEY_LISTENER"; field public static final java.lang.String SET_ORIENTATION = "android.permission.SET_ORIENTATION"; field public static final java.lang.String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED"; - field public static final java.lang.String SET_PROCESS_LIMIT = "android.permission.SET_PROCESS_LIMIT"; field public static final java.lang.String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY"; - field public static final java.lang.String SET_TIME = "android.permission.SET_TIME"; field public static final java.lang.String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"; field public static final java.lang.String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT"; field public static final java.lang.String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE"; field public static final java.lang.String SHUTDOWN = "android.permission.SHUTDOWN"; - field public static final java.lang.String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES"; - field public static final java.lang.String STATUS_BAR = "android.permission.STATUS_BAR"; field public static final java.lang.String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES"; field public static final java.lang.String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"; field public static final java.lang.String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; @@ -205,18 +174,14 @@ package android { field public static final java.lang.String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER"; field public static final java.lang.String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS"; field public static final java.lang.String UPDATE_APP_OPS_STATS = "android.permission.UPDATE_APP_OPS_STATS"; - field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; field public static final java.lang.String UPDATE_LOCK = "android.permission.UPDATE_LOCK"; field public static final java.lang.String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES"; field public static final java.lang.String USER_ACTIVITY = "android.permission.USER_ACTIVITY"; field public static final java.lang.String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK"; - field public static final java.lang.String WRITE_APN_SETTINGS = "android.permission.WRITE_APN_SETTINGS"; field public static final java.lang.String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE"; field public static final java.lang.String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"; - field public static final java.lang.String WRITE_GSERVICES = "android.permission.WRITE_GSERVICES"; field public static final java.lang.String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; field public static final java.lang.String WRITE_OBB = "android.permission.WRITE_OBB"; - field public static final java.lang.String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS"; } public static final class Manifest.permission_group { @@ -297,6 +262,7 @@ package android.app { method public void killUid(int, java.lang.String); method public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener); method public static void setPersistentVrThread(int); + method public boolean switchUser(android.os.UserHandle); } public static abstract interface ActivityManager.OnUidImportanceListener { @@ -313,7 +279,6 @@ package android.app { method public android.app.AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int, java.lang.String, java.lang.String[], long, long); method public static java.lang.String[] getOpStrs(); method public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, int[]); - method public java.util.List<android.app.AppOpsManager.PackageOps> getPackagesForOpStrs(java.lang.String[]); method public static int opToDefaultMode(java.lang.String); method public static java.lang.String opToPermission(java.lang.String); method public void setMode(java.lang.String, int, java.lang.String, int); @@ -577,10 +542,6 @@ package android.app { package android.app.admin { - public class DeviceAdminReceiver extends android.content.BroadcastReceiver { - method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent); - } - public class DevicePolicyManager { method public java.lang.String getDeviceOwner(); method public android.content.ComponentName getDeviceOwnerComponentOnAnyUser(); @@ -596,6 +557,8 @@ package android.app.admin { method public boolean isDeviceManaged(); method public boolean isDeviceProvisioned(); method public boolean isDeviceProvisioningConfigApplied(); + method public boolean isManagedKiosk(); + method public boolean isUnattendedManagedKiosk(); method public void notifyPendingSystemUpdate(long); method public void notifyPendingSystemUpdate(long, boolean); method public boolean packageHasActiveAdmins(java.lang.String); @@ -613,7 +576,12 @@ package android.app.admin { field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL"; field public static final java.lang.String EXTRA_PROVISIONING_ORGANIZATION_NAME = "android.app.extra.PROVISIONING_ORGANIZATION_NAME"; field public static final java.lang.String EXTRA_PROVISIONING_SUPPORT_URL = "android.app.extra.PROVISIONING_SUPPORT_URL"; + field public static final java.lang.String EXTRA_PROVISIONING_TRIGGER = "android.app.extra.PROVISIONING_TRIGGER"; field public static final java.lang.String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION"; + field public static final int PROVISIONING_TRIGGER_CLOUD_ENROLLMENT = 1; // 0x1 + field public static final int PROVISIONING_TRIGGER_PERSISTENT_DEVICE_OWNER = 3; // 0x3 + field public static final int PROVISIONING_TRIGGER_QR_CODE = 2; // 0x2 + field public static final int PROVISIONING_TRIGGER_UNSPECIFIED = 0; // 0x0 field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4 field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2 field public static final int STATE_USER_SETUP_FINALIZED = 3; // 0x3 @@ -622,10 +590,7 @@ package android.app.admin { } public final class SystemUpdatePolicy implements android.os.Parcelable { - method public int describeContents(); method public android.app.admin.SystemUpdatePolicy.InstallationOption getInstallationOptionAt(long); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdatePolicy> CREATOR; field public static final int TYPE_PAUSE = 4; // 0x4 } @@ -844,6 +809,8 @@ package android.app.backup { method public int restoreAll(long, android.app.backup.RestoreObserver); method public int restorePackage(java.lang.String, android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor); method public int restorePackage(java.lang.String, android.app.backup.RestoreObserver); + method public int restoreSome(long, android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor, java.lang.String[]); + method public int restoreSome(long, android.app.backup.RestoreObserver, java.lang.String[]); } public class RestoreSet implements android.os.Parcelable { @@ -875,12 +842,18 @@ package android.app.job { package android.app.role { + public abstract interface OnRoleHoldersChangedListener { + method public abstract void onRoleHoldersChanged(java.lang.String, android.os.UserHandle); + } + public final class RoleManager { + method public void addOnRoleHoldersChangedListenerAsUser(java.util.concurrent.Executor, android.app.role.OnRoleHoldersChangedListener, android.os.UserHandle); method public void addRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); method public boolean addRoleHolderFromController(java.lang.String, java.lang.String); method public void clearRoleHoldersAsUser(java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); method public java.util.List<java.lang.String> getRoleHolders(java.lang.String); method public java.util.List<java.lang.String> getRoleHoldersAsUser(java.lang.String, android.os.UserHandle); + method public void removeOnRoleHoldersChangedListenerAsUser(android.app.role.OnRoleHoldersChangedListener, android.os.UserHandle); method public void removeRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); method public boolean removeRoleHolderFromController(java.lang.String, java.lang.String); method public void setRoleNamesFromController(java.util.List<java.lang.String>); @@ -926,6 +899,7 @@ package android.app.usage { } public static final class UsageEvents.Event { + method public int getInstanceId(); method public java.lang.String getNotificationChannelId(); field public static final int NOTIFICATION_INTERRUPTION = 12; // 0xc field public static final int NOTIFICATION_SEEN = 10; // 0xa @@ -1030,6 +1004,14 @@ package android.bluetooth.le { package android.content { + public class ContentProviderClient implements java.lang.AutoCloseable android.content.ContentInterface { + method public void setDetectNotResponding(long); + } + + public abstract class ContentResolver implements android.content.ContentInterface { + method public android.graphics.drawable.Drawable getTypeDrawable(java.lang.String); + } + public abstract class Context { method public boolean bindServiceAsUser(android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle); method public abstract android.content.Context createCredentialProtectedStorageContext(); @@ -1128,6 +1110,10 @@ package android.content.pm { field public int targetSandboxVersion; } + public class CrossProfileApps { + method public void startAnyActivity(android.content.ComponentName, android.os.UserHandle); + } + public final class InstantAppInfo implements android.os.Parcelable { ctor public InstantAppInfo(android.content.pm.ApplicationInfo, java.lang.String[], java.lang.String[]); ctor public InstantAppInfo(java.lang.String, java.lang.CharSequence, java.lang.String[], java.lang.String[]); @@ -1229,6 +1215,7 @@ package android.content.pm { method public abstract boolean arePermissionsIndividuallyControlled(); method public boolean canSuspendPackage(java.lang.String); method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String); + method public android.content.pm.ApplicationInfo getApplicationInfoAsUser(java.lang.String, int, android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; method public android.content.pm.dex.ArtManager getArtManager(); method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int); method public java.lang.CharSequence getHarmfulAppWarning(java.lang.String); @@ -1244,6 +1231,9 @@ package android.content.pm { method public abstract int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle); + method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(android.content.Intent, int, android.os.UserHandle); + method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(android.content.Intent, int, android.os.UserHandle); + method public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(android.content.Intent, int, android.os.UserHandle); method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback); method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); method public deprecated void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName); @@ -1340,8 +1330,10 @@ package android.content.pm { public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { field public static final int FLAG_REMOVED = 2; // 0x2 + field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000 field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000 field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000 + field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000 field public java.lang.String backgroundPermission; field public int requestRes; } @@ -1386,7 +1378,7 @@ package android.content.pm.dex { package android.content.pm.permission { - public final class RuntimePermissionPresentationInfo implements android.os.Parcelable { + public final deprecated class RuntimePermissionPresentationInfo implements android.os.Parcelable { ctor public RuntimePermissionPresentationInfo(java.lang.CharSequence, boolean, boolean); method public int describeContents(); method public java.lang.CharSequence getLabel(); @@ -2919,6 +2911,13 @@ package android.media { field public static final int RADIO_TUNER = 1998; // 0x7ce } + public static class MediaTimestamp.Builder { + ctor public MediaTimestamp.Builder(); + ctor public MediaTimestamp.Builder(android.media.MediaTimestamp); + method public android.media.MediaTimestamp build(); + method public android.media.MediaTimestamp.Builder setMediaTimestamp(long, long, float); + } + public class PlayerProxy { method public void pause(); method public void setPan(float); @@ -2939,7 +2938,7 @@ package android.media { ctor public TimedMetaData.Builder(); ctor public TimedMetaData.Builder(android.media.TimedMetaData); method public android.media.TimedMetaData build(); - method public android.media.TimedMetaData.Builder setTimedMetaData(int, byte[]); + method public android.media.TimedMetaData.Builder setTimedMetaData(long, byte[]); } } @@ -3029,7 +3028,6 @@ package android.media.audiopolicy { package android.media.session { public final class MediaSessionManager { - method public android.media.session.ISession createSession(android.media.session.MediaSession.CallbackStub, java.lang.String, int); method public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, android.os.Handler); method public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, android.os.Handler); } @@ -3210,7 +3208,6 @@ package android.media.tv { method public void addBlockedRating(android.media.tv.TvContentRating); method public boolean captureFrame(java.lang.String, android.view.Surface, android.media.tv.TvStreamConfig); method public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(java.lang.String); - method public java.util.List<android.media.tv.TvContentRating> getBlockedRatings(); method public java.util.List<android.media.tv.TvInputHardwareInfo> getHardwareList(); method public java.util.List<android.media.tv.TvContentRatingSystemInfo> getTvContentRatingSystemList(); method public boolean isSingleSessionActive(); @@ -3456,11 +3453,9 @@ package android.net { } public class TrafficStats { - method public static void clearThreadStatsUid(); method public static void setThreadStatsTagApp(); method public static void setThreadStatsTagBackup(); method public static void setThreadStatsTagRestore(); - method public static void setThreadStatsUid(int); } public class VpnService extends android.app.Service { @@ -3482,14 +3477,6 @@ package android.net { } -package android.net.http { - - public class X509TrustManagerExtensions { - method public boolean isSameTrustConfiguration(java.lang.String, java.lang.String); - } - -} - package android.net.wifi { public deprecated class RttManager { @@ -3713,7 +3700,6 @@ package android.net.wifi { method public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks(); method public android.net.wifi.WifiConfiguration getWifiApConfiguration(); method public int getWifiApState(); - method public boolean isDeviceToApRttSupported(); method public boolean isDeviceToDeviceRttSupported(); method public boolean isPortableHotspotSupported(); method public boolean isWifiApEnabled(); @@ -4317,6 +4303,7 @@ package android.os { } public class UserManager { + method public boolean canSwitchUsers(); method public void clearSeedAccountData(); method public android.os.UserHandle getProfileParent(android.os.UserHandle); method public java.lang.String getSeedAccountName(); @@ -4332,6 +4319,7 @@ package android.os { method public boolean isManagedProfile(int); method public boolean isPrimaryUser(); method public boolean isRestrictedProfile(); + method public boolean removeUser(android.os.UserHandle); field public static final java.lang.String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED"; field public static final deprecated java.lang.String DISALLOW_OEM_UNLOCK = "no_oem_unlock"; field public static final java.lang.String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background"; @@ -4391,11 +4379,31 @@ package android.permission { method public int getTargetSdk(); } + public final class RuntimePermissionPresentationInfo implements android.os.Parcelable { + ctor public RuntimePermissionPresentationInfo(java.lang.CharSequence, boolean, boolean); + method public int describeContents(); + method public java.lang.CharSequence getLabel(); + method public boolean isGranted(); + method public boolean isStandard(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.permission.RuntimePermissionPresentationInfo> CREATOR; + } + + public abstract class RuntimePermissionPresenterService extends android.app.Service { + ctor public RuntimePermissionPresenterService(); + method public final void attachBaseContext(android.content.Context); + method public final android.os.IBinder onBind(android.content.Intent); + method public abstract int onCountPermissionApps(java.util.List<java.lang.String>, boolean, boolean); + method public abstract java.util.List<android.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(java.lang.String); + method public abstract void onRevokeRuntimePermission(java.lang.String, java.lang.String); + field public static final java.lang.String SERVICE_INTERFACE = "android.permission.RuntimePermissionPresenterService"; + } + } package android.permissionpresenterservice { - public abstract class RuntimePermissionPresenterService extends android.app.Service { + public abstract deprecated class RuntimePermissionPresenterService extends android.app.Service { ctor public RuntimePermissionPresenterService(); method public final void attachBaseContext(android.content.Context); method public final android.os.IBinder onBind(android.content.Intent); @@ -4506,6 +4514,21 @@ package android.provider { field public static final java.lang.String STATE = "state"; } + public final class DocumentsContract { + method public static boolean isManageMode(android.net.Uri); + method public static android.net.Uri setManageMode(android.net.Uri); + field public static final java.lang.String ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS"; + field public static final java.lang.String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT"; + field public static final java.lang.String EXTRA_SHOW_ADVANCED = "android.provider.extra.SHOW_ADVANCED"; + } + + public static final class DocumentsContract.Root { + field public static final int FLAG_ADVANCED = 65536; // 0x10000 + field public static final int FLAG_HAS_SETTINGS = 131072; // 0x20000 + field public static final int FLAG_REMOVABLE_SD = 262144; // 0x40000 + field public static final int FLAG_REMOVABLE_USB = 524288; // 0x80000 + } + public abstract class SearchIndexableData { ctor public SearchIndexableData(); ctor public SearchIndexableData(android.content.Context); @@ -4850,7 +4873,10 @@ package android.service.autofill { public abstract class AutofillFieldClassificationService extends android.app.Service { method public android.os.IBinder onBind(android.content.Intent); - method public float[][] onGetScores(java.lang.String, android.os.Bundle, java.util.List<android.view.autofill.AutofillValue>, java.util.List<java.lang.String>); + method public float[][] onCalculateScores(java.util.List<android.view.autofill.AutofillValue>, java.util.List<java.lang.String>, java.util.List<java.lang.String>, java.lang.String, android.os.Bundle, java.util.Map, java.util.Map); + method public deprecated float[][] onGetScores(java.lang.String, android.os.Bundle, java.util.List<android.view.autofill.AutofillValue>, java.util.List<java.lang.String>); + field public static final java.lang.String REQUIRED_ALGORITHM_EDIT_DISTANCE = "EDIT_DISTANCE"; + field public static final java.lang.String REQUIRED_ALGORITHM_EXACT_MATCH = "EXACT_MATCH"; field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillFieldClassificationService"; field public static final java.lang.String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS = "android.autofill.field_classification.available_algorithms"; field public static final java.lang.String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM = "android.autofill.field_classification.default_algorithm"; @@ -4858,6 +4884,69 @@ package android.service.autofill { } +package android.service.autofill.augmented { + + public abstract class AugmentedAutofillService extends android.app.Service { + ctor public AugmentedAutofillService(); + method public void onFillRequest(android.service.autofill.augmented.FillRequest, android.os.CancellationSignal, android.service.autofill.augmented.FillController, android.service.autofill.augmented.FillCallback); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.augmented.AugmentedAutofillService"; + } + + public final class FillCallback { + method public void onSuccess(android.service.autofill.augmented.FillResponse); + } + + public final class FillController { + method public void autofill(java.util.List<android.util.Pair<android.view.autofill.AutofillId, android.view.autofill.AutofillValue>>); + } + + public final class FillRequest { + method public android.content.ComponentName getActivityComponent(); + method public android.view.autofill.AutofillId getFocusedId(); + method public android.view.autofill.AutofillValue getFocusedValue(); + method public android.service.autofill.augmented.PresentationParams getPresentationParams(); + method public int getTaskId(); + } + + public final class FillResponse implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.autofill.augmented.FillResponse> CREATOR; + } + + public static final class FillResponse.Builder { + ctor public FillResponse.Builder(); + method public android.service.autofill.augmented.FillResponse build(); + method public android.service.autofill.augmented.FillResponse.Builder setFillWindow(android.service.autofill.augmented.FillWindow); + method public android.service.autofill.augmented.FillResponse.Builder setIgnoredIds(java.util.List<android.view.autofill.AutofillId>); + } + + public final class FillWindow implements java.lang.AutoCloseable { + ctor public FillWindow(); + method public void destroy(); + method public boolean update(android.service.autofill.augmented.PresentationParams.Area, android.view.View, long); + field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L + } + + public abstract class PresentationParams { + method public int getFlags(); + method public android.service.autofill.augmented.PresentationParams.Area getFullArea(); + method public android.service.autofill.augmented.PresentationParams.Area getSuggestionArea(); + field public static final int FLAG_HINT_GRAVITY_BOTTOM = 2; // 0x2 + field public static final int FLAG_HINT_GRAVITY_LEFT = 4; // 0x4 + field public static final int FLAG_HINT_GRAVITY_RIGHT = 8; // 0x8 + field public static final int FLAG_HINT_GRAVITY_TOP = 1; // 0x1 + field public static final int FLAG_HOST_IME = 16; // 0x10 + field public static final int FLAG_HOST_SYSTEM = 32; // 0x20 + } + + public static abstract class PresentationParams.Area { + method public android.graphics.Rect getBounds(); + method public android.service.autofill.augmented.PresentationParams.Area getSubArea(android.graphics.Rect); + } + +} + package android.service.carrier { public abstract class ApnService extends android.app.Service { @@ -4868,6 +4957,58 @@ package android.service.carrier { } +package android.service.contentcapture { + + public final class ContentCaptureEventsRequest implements android.os.Parcelable { + method public int describeContents(); + method public java.util.List<android.view.contentcapture.ContentCaptureEvent> getEvents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.contentcapture.ContentCaptureEventsRequest> CREATOR; + } + + public abstract class ContentCaptureService extends android.app.Service { + ctor public ContentCaptureService(); + method public final java.util.Set<android.content.ComponentName> getContentCaptureDisabledActivities(); + method public final java.util.Set<java.lang.String> getContentCaptureDisabledPackages(); + method public void onActivitySnapshot(android.service.contentcapture.InteractionSessionId, android.service.contentcapture.SnapshotData); + method public abstract void onContentCaptureEventsRequest(android.service.contentcapture.InteractionSessionId, android.service.contentcapture.ContentCaptureEventsRequest); + method public void onCreateInteractionSession(android.service.contentcapture.InteractionContext, android.service.contentcapture.InteractionSessionId); + method public void onDestroyInteractionSession(android.service.contentcapture.InteractionSessionId); + method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean); + method public final void setContentCaptureWhitelist(java.util.List<java.lang.String>, java.util.List<android.content.ComponentName>); + method public final void setPackageContentCaptureEnabled(java.lang.String, boolean); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.contentcapture.ContentCaptureService"; + } + + public final class InteractionContext implements android.os.Parcelable { + method public int describeContents(); + method public android.content.ComponentName getActivityComponent(); + method public int getDisplayId(); + method public int getFlags(); + method public int getTaskId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.contentcapture.InteractionContext> CREATOR; + field public static final int FLAG_DISABLED_BY_APP = 1; // 0x1 + field public static final int FLAG_DISABLED_BY_FLAG_SECURE = 2; // 0x2 + } + + public final class InteractionSessionId implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.contentcapture.InteractionSessionId> CREATOR; + } + + public final class SnapshotData implements android.os.Parcelable { + method public int describeContents(); + method public android.app.assist.AssistContent getAssistContent(); + method public android.os.Bundle getAssistData(); + method public android.app.assist.AssistStructure getAssistStructure(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.contentcapture.SnapshotData> CREATOR; + } + +} + package android.service.euicc { public final class EuiccProfileInfo implements android.os.Parcelable { @@ -4988,111 +5129,6 @@ package android.service.euicc { } -package android.service.intelligence { - - public final class ContentCaptureEventsRequest implements android.os.Parcelable { - method public int describeContents(); - method public java.util.List<android.view.intelligence.ContentCaptureEvent> getEvents(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.intelligence.ContentCaptureEventsRequest> CREATOR; - } - - public final class FillCallback { - method public void onSuccess(android.service.intelligence.FillResponse); - } - - public final class FillController { - method public void autofill(java.util.List<android.util.Pair<android.view.autofill.AutofillId, android.view.autofill.AutofillValue>>); - } - - public final class FillRequest { - method public android.view.autofill.AutofillValue getFocusedAutofillValue(); - method public android.view.autofill.AutofillId getFocusedId(); - method public android.service.intelligence.PresentationParams getPresentationParams(); - method public android.service.intelligence.InteractionSessionId getSessionId(); - } - - public final class FillResponse implements android.os.Parcelable { - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.intelligence.FillResponse> CREATOR; - } - - public static class FillResponse.Builder { - ctor public FillResponse.Builder(); - method public android.service.intelligence.FillResponse build(); - method public android.service.intelligence.FillResponse.Builder setFillWindow(android.service.intelligence.FillWindow); - method public android.service.intelligence.FillResponse.Builder setIgnoredIds(java.util.List<android.view.autofill.AutofillId>); - } - - public final class FillWindow { - ctor public FillWindow(); - method public void destroy(); - method public boolean update(android.service.intelligence.PresentationParams.Area, android.view.View, long); - field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L - } - - public final class InteractionContext implements android.os.Parcelable { - method public int describeContents(); - method public android.content.ComponentName getActivityComponent(); - method public int getDisplayId(); - method public int getFlags(); - method public int getTaskId(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.intelligence.InteractionContext> CREATOR; - field public static final int FLAG_DISABLED_BY_APP = 1; // 0x1 - field public static final int FLAG_DISABLED_BY_FLAG_SECURE = 2; // 0x2 - } - - public final class InteractionSessionId implements android.os.Parcelable { - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.intelligence.InteractionSessionId> CREATOR; - } - - public abstract class PresentationParams { - method public int getFlags(); - method public android.service.intelligence.PresentationParams.Area getFullArea(); - method public android.service.intelligence.PresentationParams.Area getSuggestionArea(); - field public static final int FLAG_HINT_GRAVITY_BOTTOM = 2; // 0x2 - field public static final int FLAG_HINT_GRAVITY_LEFT = 4; // 0x4 - field public static final int FLAG_HINT_GRAVITY_RIGHT = 8; // 0x8 - field public static final int FLAG_HINT_GRAVITY_TOP = 1; // 0x1 - field public static final int FLAG_HOST_IME = 16; // 0x10 - field public static final int FLAG_HOST_SYSTEM = 32; // 0x20 - } - - public static abstract class PresentationParams.Area { - method public android.graphics.Rect getBounds(); - method public android.service.intelligence.PresentationParams.Area getSubArea(android.graphics.Rect); - } - - public abstract class SmartSuggestionsService extends android.app.Service { - ctor public SmartSuggestionsService(); - method public final java.util.Set<android.content.ComponentName> getContentCaptureDisabledActivities(); - method public final java.util.Set<java.lang.String> getContentCaptureDisabledPackages(); - method public void onActivitySnapshot(android.service.intelligence.InteractionSessionId, android.service.intelligence.SnapshotData); - method public abstract void onContentCaptureEventsRequest(android.service.intelligence.InteractionSessionId, android.service.intelligence.ContentCaptureEventsRequest); - method public void onCreateInteractionSession(android.service.intelligence.InteractionContext, android.service.intelligence.InteractionSessionId); - method public void onDestroyInteractionSession(android.service.intelligence.InteractionSessionId); - method public void onFillRequest(android.service.intelligence.InteractionSessionId, android.service.intelligence.FillRequest, android.os.CancellationSignal, android.service.intelligence.FillController, android.service.intelligence.FillCallback); - method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean); - method public final void setContentCaptureWhitelist(java.util.List<java.lang.String>, java.util.List<android.content.ComponentName>); - method public final void setPackageContentCaptureEnabled(java.lang.String, boolean); - field public static final java.lang.String SERVICE_INTERFACE = "android.service.intelligence.SmartSuggestionsService"; - } - - public final class SnapshotData implements android.os.Parcelable { - method public int describeContents(); - method public android.app.assist.AssistContent getAssistContent(); - method public android.os.Bundle getAssistData(); - method public android.app.assist.AssistStructure getAssistStructure(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.intelligence.SnapshotData> CREATOR; - } - -} - package android.service.notification { public final class Adjustment implements android.os.Parcelable { @@ -5114,28 +5150,6 @@ package android.service.notification { field public static final java.lang.String KEY_USER_SENTIMENT = "key_user_sentiment"; } - public final class Condition implements android.os.Parcelable { - ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int); - method public android.service.notification.Condition copy(); - method public static boolean isValidId(android.net.Uri, java.lang.String); - method public static android.net.Uri.Builder newId(android.content.Context); - method public static java.lang.String relevanceToString(int); - method public static java.lang.String stateToString(int); - field public static final int FLAG_RELEVANT_ALWAYS = 2; // 0x2 - field public static final int FLAG_RELEVANT_NOW = 1; // 0x1 - field public static final java.lang.String SCHEME = "condition"; - field public static final int STATE_ERROR = 3; // 0x3 - field public static final int STATE_UNKNOWN = 2; // 0x2 - field public final int flags; - field public final int icon; - field public final java.lang.String line1; - field public final java.lang.String line2; - } - - public abstract class ConditionProviderService extends android.app.Service { - method public void onRequestConditions(int); - } - public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService { ctor public NotificationAssistantService(); method public final void adjustNotification(android.service.notification.Adjustment); @@ -5558,6 +5572,10 @@ package android.telecom { field public static final int CAPABILITY_MULTI_USER = 32; // 0x20 } + public final class PhoneAccountSuggestion implements android.os.Parcelable { + ctor public PhoneAccountSuggestion(android.telecom.PhoneAccountHandle, int, boolean); + } + public final class RemoteConference { method public deprecated void setAudioState(android.telecom.AudioState); } @@ -5614,7 +5632,6 @@ package android.telecom { method public void clearPhoneAccounts(); method public android.telecom.TelecomAnalytics dumpAnalytics(); method public void enablePhoneAccount(android.telecom.PhoneAccountHandle, boolean); - method public boolean endCall(); method public java.util.List<android.telecom.PhoneAccountHandle> getAllPhoneAccountHandles(); method public java.util.List<android.telecom.PhoneAccount> getAllPhoneAccounts(); method public int getAllPhoneAccountsCount(); @@ -5626,7 +5643,6 @@ package android.telecom { method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(java.lang.String); method public boolean isInEmergencyCall(); method public boolean isRinging(); - method public boolean isTtySupported(); method public boolean setDefaultDialer(java.lang.String); field public static final java.lang.String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT"; field public static final java.lang.String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT"; @@ -5651,7 +5667,6 @@ package android.telephony { method public void overrideConfig(int, android.os.PersistableBundle); method public void updateConfigForPhoneId(int, java.lang.String); field public static final java.lang.String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string"; - field public static final java.lang.String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string"; } public class MbmsDownloadSession implements java.lang.AutoCloseable { @@ -5766,7 +5781,6 @@ package android.telephony { public final class SmsManager { method public void sendMultipartTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>); - method public void sendTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent); field public static final int RESULT_CANCELLED = 23; // 0x17 field public static final int RESULT_ENCODING_ERROR = 18; // 0x12 field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 6; // 0x6 @@ -5793,15 +5807,9 @@ package android.telephony { public class SubscriptionManager { method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList(); - method public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int); method public void requestEmbeddedSubscriptionInfoListRefresh(); method public void setDefaultDataSubId(int); method public void setDefaultSmsSubId(int); - method public void setSubscriptionOverrideCongested(int, boolean, long); - method public void setSubscriptionOverrideUnmetered(int, boolean, long); - method public void setSubscriptionPlans(int, java.util.List<android.telephony.SubscriptionPlan>); - field public static final java.lang.String ACTION_MANAGE_SUBSCRIPTION_PLANS = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS"; - field public static final java.lang.String ACTION_REFRESH_SUBSCRIPTION_PLANS = "android.telephony.action.REFRESH_SUBSCRIPTION_PLANS"; field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI; field public static final android.net.Uri VT_ENABLED_CONTENT_URI; field public static final android.net.Uri WFC_ENABLED_CONTENT_URI; @@ -5810,37 +5818,10 @@ package android.telephony { field public static final android.net.Uri WFC_ROAMING_MODE_CONTENT_URI; } - public final class SubscriptionPlan implements android.os.Parcelable { - method public java.util.Iterator<android.util.Range<java.time.ZonedDateTime>> cycleIterator(); - method public int describeContents(); - method public int getDataLimitBehavior(); - method public long getDataLimitBytes(); - method public long getDataUsageBytes(); - method public long getDataUsageTime(); - method public java.lang.CharSequence getSummary(); - method public java.lang.CharSequence getTitle(); - method public void writeToParcel(android.os.Parcel, int); - field public static final long BYTES_UNKNOWN = -1L; // 0xffffffffffffffffL - field public static final long BYTES_UNLIMITED = 9223372036854775807L; // 0x7fffffffffffffffL - field public static final android.os.Parcelable.Creator<android.telephony.SubscriptionPlan> CREATOR; - field public static final int LIMIT_BEHAVIOR_BILLED = 1; // 0x1 - field public static final int LIMIT_BEHAVIOR_DISABLED = 0; // 0x0 - field public static final int LIMIT_BEHAVIOR_THROTTLED = 2; // 0x2 - field public static final int LIMIT_BEHAVIOR_UNKNOWN = -1; // 0xffffffff - field public static final long TIME_UNKNOWN = -1L; // 0xffffffffffffffffL - } - public static class SubscriptionPlan.Builder { - method public android.telephony.SubscriptionPlan build(); - method public static android.telephony.SubscriptionPlan.Builder createNonrecurring(java.time.ZonedDateTime, java.time.ZonedDateTime); - method public static android.telephony.SubscriptionPlan.Builder createRecurring(java.time.ZonedDateTime, java.time.Period); method public static deprecated android.telephony.SubscriptionPlan.Builder createRecurringDaily(java.time.ZonedDateTime); method public static deprecated android.telephony.SubscriptionPlan.Builder createRecurringMonthly(java.time.ZonedDateTime); method public static deprecated android.telephony.SubscriptionPlan.Builder createRecurringWeekly(java.time.ZonedDateTime); - method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int); - method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long); - method public android.telephony.SubscriptionPlan.Builder setSummary(java.lang.CharSequence); - method public android.telephony.SubscriptionPlan.Builder setTitle(java.lang.CharSequence); } public final class TelephonyHistogram implements android.os.Parcelable { @@ -5898,6 +5879,7 @@ package android.telephony { method public int getVoiceActivationState(); method public boolean handlePinMmi(java.lang.String); method public boolean handlePinMmiForSubscriber(int, java.lang.String); + method public boolean isCurrentPotentialEmergencyNumber(java.lang.String); method public boolean isDataConnectivityPossible(); method public deprecated boolean isIdle(); method public deprecated boolean isOffhook(); @@ -6759,12 +6741,6 @@ package android.telephony.ims { method public android.telephony.ims.ImsSsInfo.Builder setProvisionStatus(int); } - public static abstract class ImsSsInfo.ClirInterrogationStatus implements java.lang.annotation.Annotation { - } - - public static abstract class ImsSsInfo.ClirOutgoingState implements java.lang.annotation.Annotation { - } - public final class ImsStreamMediaProfile implements android.os.Parcelable { ctor public ImsStreamMediaProfile(int, int, int, int, int); method public void copyFrom(android.telephony.ims.ImsStreamMediaProfile); @@ -7264,12 +7240,13 @@ package android.view { package android.view.accessibility { public final class AccessibilityManager { + method public int getAccessibilityWindowId(android.os.IBinder); method public void performAccessibilityShortcut(); } } -package android.view.intelligence { +package android.view.contentcapture { public final class ContentCaptureEvent implements android.os.Parcelable { method public int describeContents(); @@ -7278,13 +7255,13 @@ package android.view.intelligence { method public android.view.autofill.AutofillId getId(); method public java.lang.CharSequence getText(); method public int getType(); - method public android.view.intelligence.ViewNode getViewNode(); + method public android.view.contentcapture.ViewNode getViewNode(); method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.view.intelligence.ContentCaptureEvent> CREATOR; - field public static final int TYPE_ACTIVITY_PAUSED = 3; // 0x3 - field public static final int TYPE_ACTIVITY_RESUMED = 2; // 0x2 - field public static final int TYPE_ACTIVITY_STARTED = 1; // 0x1 - field public static final int TYPE_ACTIVITY_STOPPED = 4; // 0x4 + field public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureEvent> CREATOR; + field public static final deprecated int TYPE_ACTIVITY_PAUSED = 3; // 0x3 + field public static final deprecated int TYPE_ACTIVITY_RESUMED = 2; // 0x2 + field public static final deprecated int TYPE_ACTIVITY_STARTED = 1; // 0x1 + field public static final deprecated int TYPE_ACTIVITY_STOPPED = 4; // 0x4 field public static final int TYPE_VIEW_APPEARED = 5; // 0x5 field public static final int TYPE_VIEW_DISAPPEARED = 6; // 0x6 field public static final int TYPE_VIEW_TEXT_CHANGED = 7; // 0x7 diff --git a/api/test-current.txt b/api/test-current.txt index 46cbb52f6efa..627ef22a5d56 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5,11 +5,12 @@ package android { field public static final java.lang.String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final java.lang.String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final java.lang.String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; - field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION"; field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; field public static final java.lang.String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final java.lang.String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; field public static final java.lang.String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"; + field public static final java.lang.String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; + field public static final java.lang.String WRITE_OBB = "android.permission.WRITE_OBB"; } } @@ -276,6 +277,23 @@ package android.app.backup { } +package android.app.role { + + public final class RoleManager { + method public void addRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); + method public void clearRoleHoldersAsUser(java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); + method public java.util.List<java.lang.String> getRoleHolders(java.lang.String); + method public java.util.List<java.lang.String> getRoleHoldersAsUser(java.lang.String, android.os.UserHandle); + method public void removeRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); + } + + public abstract interface RoleManagerCallback { + method public abstract void onFailure(); + method public abstract void onSuccess(); + } + +} + package android.app.usage { public class NetworkStatsManager { @@ -299,7 +317,11 @@ package android.bluetooth { package android.content { - public abstract class ContentResolver { + public class ContentProviderClient implements java.lang.AutoCloseable android.content.ContentInterface { + method public void setDetectNotResponding(long); + } + + public abstract class ContentResolver implements android.content.ContentInterface { method public static java.lang.String[] getSyncAdapterPackagesForAuthorityAsUser(java.lang.String, int); } @@ -340,6 +362,7 @@ package android.content.pm { method public abstract java.lang.String getPermissionControllerPackageName(); method public abstract java.lang.String getServicesSystemSharedLibraryPackageName(); method public abstract java.lang.String getSharedSystemSharedLibraryPackageName(); + method public java.lang.String getWellbeingPackageName(); method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); field public static final java.lang.String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage"; @@ -351,8 +374,10 @@ package android.content.pm { } public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { + field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000 field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000 field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000 + field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000 field public java.lang.String backgroundPermission; } @@ -983,6 +1008,11 @@ package android.provider { field public static final android.net.Uri CORP_CONTENT_URI; } + public final class MediaStore { + method public static void deleteContributedMedia(android.content.Context, java.lang.String, android.os.UserHandle) throws java.io.IOException; + method public static long getContributedMediaSize(android.content.Context, java.lang.String, android.os.UserHandle) throws java.io.IOException; + } + public final class Settings { field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS"; } @@ -1066,6 +1096,15 @@ package android.security.keystore { package android.service.autofill { + public abstract class AutofillFieldClassificationService extends android.app.Service { + method public android.os.IBinder onBind(android.content.Intent); + field public static final java.lang.String REQUIRED_ALGORITHM_EDIT_DISTANCE = "EDIT_DISTANCE"; + field public static final java.lang.String REQUIRED_ALGORITHM_EXACT_MATCH = "EXACT_MATCH"; + field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillFieldClassificationService"; + field public static final java.lang.String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS = "android.autofill.field_classification.available_algorithms"; + field public static final java.lang.String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM = "android.autofill.field_classification.default_algorithm"; + } + public final class CharSequenceTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation { method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception; } @@ -1122,6 +1161,10 @@ package android.service.autofill { method public android.view.autofill.AutofillValue sanitize(android.view.autofill.AutofillValue); } + public final class UserData implements android.os.Parcelable { + method public android.util.ArrayMap<java.lang.String, java.lang.String> getFieldClassificationAlgorithms(); + } + public abstract interface ValueFinder { method public default java.lang.String findByAutofillId(android.view.autofill.AutofillId); method public abstract android.view.autofill.AutofillValue findRawValueByAutofillId(android.view.autofill.AutofillId); @@ -1240,6 +1283,10 @@ package android.telecom { ctor public CallAudioState(boolean, int, int, android.bluetooth.BluetoothDevice, java.util.Collection<android.bluetooth.BluetoothDevice>); } + public final class PhoneAccountSuggestion implements android.os.Parcelable { + ctor public PhoneAccountSuggestion(android.telecom.PhoneAccountHandle, int, boolean); + } + } package android.telephony { @@ -1663,11 +1710,6 @@ package android.view { method public static int getLongPressTooltipHideTimeout(); } - public final class ViewTreeObserver { - method public void registerFrameCommitCallback(java.lang.Runnable); - method public boolean unregisterFrameCommitCallback(java.lang.Runnable); - } - public abstract interface WindowManager implements android.view.ViewManager { method public default void setShouldShowIme(int, boolean); method public default void setShouldShowSystemDecors(int, boolean); diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 3723fce9d4f0..e3748f1653ab 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -201,7 +201,7 @@ public class Bmgr { private void doEnabled(@UserIdInt int userId) { try { - boolean isEnabled = mBmgr.isBackupEnabled(); + boolean isEnabled = mBmgr.isBackupEnabledForUser(userId); System.out.println("Backup Manager currently " + enableToString(isEnabled)); } catch (RemoteException e) { @@ -219,7 +219,7 @@ public class Bmgr { try { boolean enable = Boolean.parseBoolean(arg); - mBmgr.setBackupEnabled(enable); + mBmgr.setBackupEnabledForUser(userId, enable); System.out.println("Backup Manager now " + enableToString(enable)); } catch (NumberFormatException e) { showUsage(); @@ -232,7 +232,7 @@ public class Bmgr { void doRun(@UserIdInt int userId) { try { - mBmgr.backupNow(); + mBmgr.backupNowForUser(userId); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); @@ -416,7 +416,8 @@ public class Bmgr { (monitorState != Monitor.OFF) ? new BackupMonitor(monitorState == Monitor.VERBOSE) : null; - int err = mBmgr.requestBackup( + int err = mBmgr.requestBackupForUser( + userId, packages.toArray(new String[packages.size()]), observer, monitor, @@ -477,7 +478,7 @@ public class Bmgr { String arg = nextArg(); if ("backups".equals(arg)) { try { - mBmgr.cancelBackups(); + mBmgr.cancelBackupsForUser(userId); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java index 52a2ab407f91..55dbc17dba5d 100644 --- a/cmds/content/src/com/android/commands/content/Content.java +++ b/cmds/content/src/com/android/commands/content/Content.java @@ -557,7 +557,7 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { - Bundle result = provider.call(null, mMethod, mArg, mExtras); + Bundle result = provider.call(null, mUri.getAuthority(), mMethod, mArg, mExtras); if (result != null) { result.size(); // unpack } diff --git a/cmds/device_config/Android.mk b/cmds/device_config/Android.mk new file mode 100644 index 000000000000..4041e01927df --- /dev/null +++ b/cmds/device_config/Android.mk @@ -0,0 +1,10 @@ +# Copyright 2018 The Android Open Source Project +# +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := device_config +LOCAL_SRC_FILES := device_config +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE_TAGS := optional +include $(BUILD_PREBUILT) diff --git a/cmds/device_config/device_config b/cmds/device_config/device_config new file mode 100755 index 000000000000..a949bd528263 --- /dev/null +++ b/cmds/device_config/device_config @@ -0,0 +1,2 @@ +#!/system/bin/sh +cmd device_config "$@" diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp index b3e287bae76a..d4fdf85491d3 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.cpp +++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp @@ -45,6 +45,7 @@ static const char* UHID_PATH = "/dev/uhid"; static struct { jmethodID onDeviceOpen; + jmethodID onDeviceGetReport; jmethodID onDeviceError; } gDeviceCallbackClassInfo; @@ -82,6 +83,13 @@ void DeviceCallback::onDeviceOpen() { checkAndClearException(env, "onDeviceOpen"); } +void DeviceCallback::onDeviceGetReport(uint32_t requestId, uint8_t reportId) { + JNIEnv* env = getJNIEnv(); + env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceGetReport, + requestId, reportId); + checkAndClearException(env, "onDeviceGetReport"); +} + JNIEnv* DeviceCallback::getJNIEnv() { JNIEnv* env; mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); @@ -103,8 +111,7 @@ Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, return nullptr; } - struct uhid_event ev; - memset(&ev, 0, sizeof(ev)); + struct uhid_event ev = {}; ev.type = UHID_CREATE2; strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name)); memcpy(&ev.u.create2.rd_data, descriptor.data(), @@ -152,8 +159,7 @@ Device::~Device() { } else { LOGE("Could not remove fd, ALooper_forThread() returned NULL!"); } - struct uhid_event ev; - memset(&ev, 0, sizeof(ev)); + struct uhid_event ev = {}; ev.type = UHID_DESTROY; TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev))); ::close(mFd); @@ -166,8 +172,7 @@ void Device::sendReport(const std::vector<uint8_t>& report) const { return; } - struct uhid_event ev; - memset(&ev, 0, sizeof(ev)); + struct uhid_event ev = {}; ev.type = UHID_INPUT2; ev.u.input2.size = report.size(); memcpy(&ev.u.input2.data, report.data(), report.size() * sizeof(ev.u.input2.data[0])); @@ -177,6 +182,20 @@ void Device::sendReport(const std::vector<uint8_t>& report) const { } } +void Device::sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>& report) const { + struct uhid_event ev = {}; + ev.type = UHID_GET_REPORT_REPLY; + ev.u.get_report_reply.id = id; + ev.u.get_report_reply.err = report.size() == 0 ? EIO : 0; + ev.u.get_report_reply.size = report.size(); + memcpy(&ev.u.get_report_reply.data, report.data(), + report.size() * sizeof(ev.u.get_report_reply.data[0])); + ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev))); + if (ret < 0 || ret != sizeof(ev)) { + LOGE("Failed to send hid event (UHID_GET_REPORT_REPLY): %s", strerror(errno)); + } +} + int Device::handleEvents(int events) { if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { LOGE("uhid node was closed or an error occurred. events=0x%x", events); @@ -193,6 +212,11 @@ int Device::handleEvents(int events) { if (ev.type == UHID_OPEN) { mDeviceCallback->onDeviceOpen(); + } else if (ev.type == UHID_GET_REPORT) { + mDeviceCallback->onDeviceGetReport(ev.u.get_report.id, ev.u.get_report.rnum); + } else if (ev.type == UHID_SET_REPORT) { + LOGE("UHID_SET_REPORT is currently not supported"); + return 0; } return 1; @@ -201,9 +225,13 @@ int Device::handleEvents(int events) { } // namespace uhid std::vector<uint8_t> getData(JNIEnv* env, jbyteArray javaArray) { + std::vector<uint8_t> data; + if (javaArray == nullptr) { + return data; + } + ScopedByteArrayRO scopedArray(env, javaArray); size_t size = scopedArray.size(); - std::vector<uint8_t> data; data.reserve(size); for (size_t i = 0; i < size; i++) { data.push_back(static_cast<uint8_t>(scopedArray[i])); @@ -237,6 +265,17 @@ static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr, jbyteArray ra } } +static void sendGetFeatureReportReply(JNIEnv* env, jclass /* clazz */, jlong ptr, jint id, + jbyteArray rawReport) { + uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr); + if (d) { + std::vector<uint8_t> report = getData(env, rawReport); + d->sendGetFeatureReportReply(id, report); + } else { + LOGE("Could not send get feature report reply, Device* is null!"); + } +} + static void closeDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) { uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr); if (d) { @@ -250,6 +289,8 @@ static JNINativeMethod sMethods[] = { "Lcom/android/commands/hid/Device$DeviceCallback;)J", reinterpret_cast<void*>(openDevice) }, { "nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport) }, + { "nativeSendGetFeatureReportReply", "(JI[B)V", + reinterpret_cast<void*>(sendGetFeatureReportReply) }, { "nativeCloseDevice", "(J)V", reinterpret_cast<void*>(closeDevice) }, }; @@ -261,6 +302,8 @@ int register_com_android_commands_hid_Device(JNIEnv* env) { } uhid::gDeviceCallbackClassInfo.onDeviceOpen = env->GetMethodID(clazz, "onDeviceOpen", "()V"); + uhid::gDeviceCallbackClassInfo.onDeviceGetReport = + env->GetMethodID(clazz, "onDeviceGetReport", "(II)V"); uhid::gDeviceCallbackClassInfo.onDeviceError = env->GetMethodID(clazz, "onDeviceError", "()V"); if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL || diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h index 61a1f760697f..892c7cd12953 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.h +++ b/cmds/hid/jni/com_android_commands_hid_Device.h @@ -28,6 +28,7 @@ public: ~DeviceCallback(); void onDeviceOpen(); + void onDeviceGetReport(uint32_t requestId, uint8_t reportId); void onDeviceError(); private: @@ -45,6 +46,7 @@ public: ~Device(); void sendReport(const std::vector<uint8_t>& report) const; + void sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>& report) const; void close(); int handleEvents(int events); diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java index 8c52a8ed1e09..616d411ef7bb 100644 --- a/cmds/hid/src/com/android/commands/hid/Device.java +++ b/cmds/hid/src/com/android/commands/hid/Device.java @@ -23,6 +23,7 @@ import android.os.Message; import android.os.MessageQueue; import android.os.SystemClock; import android.util.Log; +import android.util.SparseArray; import com.android.internal.os.SomeArgs; @@ -31,11 +32,14 @@ public class Device { private static final int MSG_OPEN_DEVICE = 1; private static final int MSG_SEND_REPORT = 2; - private static final int MSG_CLOSE_DEVICE = 3; + private static final int MSG_SEND_GET_FEATURE_REPORT_REPLY = 3; + private static final int MSG_CLOSE_DEVICE = 4; private final int mId; private final HandlerThread mThread; private final DeviceHandler mHandler; + // mFeatureReports is limited to 256 entries, because the report number is 8-bit + private final SparseArray<byte[]> mFeatureReports; private long mTimeToSend; private final Object mCond = new Object(); @@ -47,13 +51,16 @@ public class Device { private static native long nativeOpenDevice(String name, int id, int vid, int pid, byte[] descriptor, DeviceCallback callback); private static native void nativeSendReport(long ptr, byte[] data); + private static native void nativeSendGetFeatureReportReply(long ptr, int id, byte[] data); private static native void nativeCloseDevice(long ptr); - public Device(int id, String name, int vid, int pid, byte[] descriptor, byte[] report) { + public Device(int id, String name, int vid, int pid, byte[] descriptor, + byte[] report, SparseArray<byte[]> featureReports) { mId = id; mThread = new HandlerThread("HidDeviceHandler"); mThread.start(); mHandler = new DeviceHandler(mThread.getLooper()); + mFeatureReports = featureReports; SomeArgs args = SomeArgs.obtain(); args.argi1 = id; args.argi2 = vid; @@ -113,6 +120,13 @@ public class Device { Log.e(TAG, "Tried to send report to closed device."); } break; + case MSG_SEND_GET_FEATURE_REPORT_REPLY: + if (mPtr != 0) { + nativeSendGetFeatureReportReply(mPtr, msg.arg1, (byte[]) msg.obj); + } else { + Log.e(TAG, "Tried to send feature report reply to closed device."); + } + break; case MSG_CLOSE_DEVICE: if (mPtr != 0) { nativeCloseDevice(mPtr); @@ -145,6 +159,23 @@ public class Device { mHandler.resumeEvents(); } + public void onDeviceGetReport(int requestId, int reportId) { + byte[] report = mFeatureReports.get(reportId); + + if (report == null) { + Log.e(TAG, "Requested feature report " + reportId + " is not specified"); + } + + Message msg; + msg = mHandler.obtainMessage(MSG_SEND_GET_FEATURE_REPORT_REPLY, requestId, 0, report); + + // Message is set to asynchronous so it won't be blocked by synchronization + // barrier during UHID_OPEN. This is necessary for drivers that do + // UHID_GET_REPORT requests during probe. + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, mTimeToSend); + } + public void onDeviceError() { Log.e(TAG, "Device error occurred, closing /dev/uhid"); Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE); diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java index c6a37bd3c48f..746e37289076 100644 --- a/cmds/hid/src/com/android/commands/hid/Event.java +++ b/cmds/hid/src/com/android/commands/hid/Event.java @@ -19,6 +19,7 @@ package com.android.commands.hid; import android.util.JsonReader; import android.util.JsonToken; import android.util.Log; +import android.util.SparseArray; import java.io.InputStreamReader; import java.io.IOException; @@ -39,6 +40,7 @@ public class Event { private int mVid; private int mPid; private byte[] mReport; + private SparseArray<byte[]> mFeatureReports; private int mDuration; public int getId() { @@ -69,6 +71,10 @@ public class Event { return mReport; } + public SparseArray<byte[]> getFeatureReports() { + return mFeatureReports; + } + public int getDuration() { return mDuration; } @@ -81,6 +87,7 @@ public class Event { + ", vid=" + mVid + ", pid=" + mPid + ", report=" + Arrays.toString(mReport) + + ", feature_reports=" + mFeatureReports.toString() + ", duration=" + mDuration + "}"; } @@ -112,6 +119,10 @@ public class Event { mEvent.mReport = report; } + public void setFeatureReports(SparseArray<byte[]> reports) { + mEvent.mFeatureReports = reports; + } + public void setVid(int vid) { mEvent.mVid = vid; } @@ -185,6 +196,9 @@ public class Event { case "report": eb.setReport(readData()); break; + case "feature_reports": + eb.setFeatureReports(readFeatureReports()); + break; case "duration": eb.setDuration(readInt()); break; @@ -234,6 +248,47 @@ public class Event { return Integer.decode(val); } + private SparseArray<byte[]> readFeatureReports() + throws IllegalStateException, IOException { + SparseArray<byte[]> featureReports = new SparseArray(); + try { + mReader.beginArray(); + while (mReader.hasNext()) { + // If "id" is not specified, it defaults to 0, which means + // report does not contain report ID (based on HID specs). + int id = 0; + byte[] data = null; + mReader.beginObject(); + while (mReader.hasNext()) { + String name = mReader.nextName(); + switch (name) { + case "id": + id = readInt(); + break; + case "data": + data = readData(); + break; + default: + consumeRemainingElements(); + mReader.endObject(); + throw new IllegalStateException("Invalid key in feature report: " + + name); + } + } + mReader.endObject(); + if (data != null) + featureReports.put(id, data); + } + mReader.endArray(); + } catch (IllegalStateException|NumberFormatException e) { + consumeRemainingElements(); + mReader.endArray(); + throw new IllegalStateException("Encountered malformed data.", e); + } finally { + return featureReports; + } + } + private void consumeRemainingElements() throws IOException { while (mReader.hasNext()) { mReader.skipValue(); diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java index 234e47f12dee..54ac1b0733ff 100644 --- a/cmds/hid/src/com/android/commands/hid/Hid.java +++ b/cmds/hid/src/com/android/commands/hid/Hid.java @@ -119,7 +119,7 @@ public class Hid { } int id = e.getId(); Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), - e.getDescriptor(), e.getReport()); + e.getDescriptor(), e.getReport(), e.getFeatureReports()); mDevices.append(id, d); } diff --git a/cmds/incident_helper/OWNERS b/cmds/incident_helper/OWNERS index 1a68a32c4308..cede4eae50ad 100644 --- a/cmds/incident_helper/OWNERS +++ b/cmds/incident_helper/OWNERS @@ -1,2 +1,3 @@ -jinyithu@google.com +joeo@google.com kwekua@google.com +yanmin@google.com diff --git a/cmds/incident_helper/tests/KernelWakesParser_test.cpp b/cmds/incident_helper/tests/KernelWakesParser_test.cpp index f92d81361eab..573ca4f632e0 100644 --- a/cmds/incident_helper/tests/KernelWakesParser_test.cpp +++ b/cmds/incident_helper/tests/KernelWakesParser_test.cpp @@ -84,9 +84,9 @@ TEST_F(KernelWakesParserTest, Normal) { record1->set_event_count(8); record1->set_wakeup_count(0); record1->set_expire_count(0); - record1->set_active_since(0l); - record1->set_total_time(0l); - record1->set_max_time(0l); + record1->set_active_since(0L); + record1->set_total_time(0L); + record1->set_max_time(0L); record1->set_last_change(131348LL); record1->set_prevent_suspend_time(0LL); @@ -96,9 +96,9 @@ TEST_F(KernelWakesParserTest, Normal) { record2->set_event_count(143); record2->set_wakeup_count(0); record2->set_expire_count(0); - record2->set_active_since(0l); - record2->set_total_time(123l); - record2->set_max_time(3l); + record2->set_active_since(0L); + record2->set_total_time(123L); + record2->set_max_time(3L); record2->set_last_change(2067286206LL); record2->set_prevent_suspend_time(0LL); diff --git a/cmds/incidentd/OWNERS b/cmds/incidentd/OWNERS index 1a68a32c4308..cede4eae50ad 100644 --- a/cmds/incidentd/OWNERS +++ b/cmds/incidentd/OWNERS @@ -1,2 +1,3 @@ -jinyithu@google.com +joeo@google.com kwekua@google.com +yanmin@google.com diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp index 74cda1da7216..04819ec75a09 100644 --- a/cmds/incidentd/src/FdBuffer.cpp +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -18,7 +18,7 @@ #include "FdBuffer.h" -#include <cutils/log.h> +#include <log/log.h> #include <utils/SystemClock.h> #include <fcntl.h> diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index 3d892adcb195..f8fb4a676ba0 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -29,7 +29,7 @@ #include <binder/IResultReceiver.h> #include <binder/IServiceManager.h> #include <binder/IShellCallback.h> -#include <cutils/log.h> +#include <log/log.h> #include <private/android_filesystem_config.h> #include <utils/Looper.h> diff --git a/cmds/incidentd/src/PrivacyBuffer.cpp b/cmds/incidentd/src/PrivacyBuffer.cpp index 6967e3391fb8..08f535db01fa 100644 --- a/cmds/incidentd/src/PrivacyBuffer.cpp +++ b/cmds/incidentd/src/PrivacyBuffer.cpp @@ -21,7 +21,7 @@ #include <android-base/file.h> #include <android/util/protobuf.h> -#include <cutils/log.h> +#include <log/log.h> namespace android { namespace os { diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 41a202109713..f58caff76d27 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -30,6 +30,7 @@ import "frameworks/base/core/proto/android/server/enums.proto"; import "frameworks/base/core/proto/android/server/location/enums.proto"; import "frameworks/base/core/proto/android/service/procstats_enum.proto"; import "frameworks/base/core/proto/android/stats/enums.proto"; +import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto"; import "frameworks/base/core/proto/android/stats/launcher/launcher.proto"; import "frameworks/base/core/proto/android/telecomm/enums.proto"; import "frameworks/base/core/proto/android/telephony/enums.proto"; @@ -159,6 +160,15 @@ message Atom { PhenotypeFlagStateChanged phenotype_flag_state_changed = 101; BinaryPushStateChanged binary_push_state_changed = 102; DevicePolicyEvent device_policy_event = 103; + DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104; + DocsUIFileOperationCopyMoveModeReported docs_ui_file_op_copy_move_mode_reported = 105; + DocsUIFileOperationFailureReported docs_ui_file_op_failure = 106; + DocsUIFileOperationReported docs_ui_provider_file_op = 107; + DocsUIInvalidScopedAccessRequestReported docs_ui_invalid_scoped_access_request = 108; + DocsUILaunchReported docs_ui_launch_reported = 109; + DocsUIRootVisitedReported docs_ui_root_visited = 110; + DocsUIStartupMsReported docs_ui_startup_ms = 111; + DocsUIUserActionReported docs_ui_user_action_reported = 112; } // Pulled events will start at field 10000. @@ -1207,6 +1217,12 @@ message UsbDeviceAttached { optional bool has_audio = 3; optional bool has_hid = 4; optional bool has_storage = 5; + enum State { + STATE_DISCONNECTED = 0; + STATE_CONNECTED = 1; + } + optional State state = 6; + optional int64 last_connect_duration_ms = 7; } @@ -1255,14 +1271,18 @@ message BluetoothConnectionStateChanged { * Logs when something is plugged into or removed from the USB-C connector. * * Logged from: - * Vendor USB HAL. + * UsbService */ message UsbConnectorStateChanged { enum State { - DISCONNECTED = 0; - CONNECTED = 1; + STATE_DISCONNECTED = 0; + STATE_CONNECTED = 1; } optional State state = 1; + optional string id = 2; + // Last active session in ms. + // 0 when the port is in connected state. + optional int64 last_connect_duration_millis = 3; } /** @@ -1501,17 +1521,17 @@ message SettingsUIChanged { /** * Where the action is happening */ - optional android.app.settings.PageId pageId = 3; + optional android.app.settings.PageId page_id = 3; /** * What preference changed in this event. */ - optional string changedPreferenceKey = 4; + optional string changed_preference_key = 4; /** * The new value of the changed preference. */ - optional int64 changedPreferenceIntValue = 5; + optional int64 changed_preference_int_value = 5; } /** @@ -3467,3 +3487,103 @@ message DevicePolicyEvent { // A parameter specifying a list of package names, bundle extras or string parameters. optional android.stats.devicepolicy.StringList string_list_value = 6 [(log_mode) = MODE_BYTES]; } + +/** + * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up. + * + * Logged from: + * package/app/DocumentsUI/src/com/android/documentsui/Metrics.java + */ +message DocsUILaunchReported { + optional android.stats.docsui.LaunchAction launch_action = 1; + optional bool has_initial_uri = 2; + optional android.stats.docsui.MimeType mime_type = 3; + optional android.stats.docsui.Root initial_root = 4; +} + +/** + * Logs root/app visited event in file managers/picker. Call this when the user + * taps on root/app in hamburger menu. + * + * Logged from: + * package/app/DocumentsUI/src/com/android/documentsui/Metrics.java + */ +message DocsUIRootVisitedReported { + optional android.stats.docsui.ContextScope scope = 1; + optional android.stats.docsui.Root root = 2; +} + +/** + * Logs file operation stats. Call this when a file operation has completed. + * + * Logged from: + * package/app/DocumentsUI/src/com/android/documentsui/Metrics.java + */ +message DocsUIFileOperationReported { + optional android.stats.docsui.Provider provider = 1; + optional android.stats.docsui.FileOperation file_op = 2; +} + +/** + * Logs file operation stats. Call this when a copy/move operation has completed with a specific + * mode. + * + * Logged from: + * package/app/DocumentsUI/src/com/android/documentsui/Metrics.java + */ +message DocsUIFileOperationCopyMoveModeReported { + optional android.stats.docsui.FileOperation file_op = 1; + optional android.stats.docsui.CopyMoveOpMode mode = 2; +} + + +/** + * Logs file sub operation stats. Call this when a file operation has failed. + * + * Logged from: + * package/app/DocumentsUI/src/com/android/documentsui/Metrics.java + */ +message DocsUIFileOperationFailureReported { + optional android.stats.docsui.Authority authority = 1; + optional android.stats.docsui.SubFileOperation sub_op = 2; +} + +/** +* Logs the cancellation of a file operation. Call this when a job is canceled +* +* Logged from: +* package/app/DocumentsUI/src/com/android/documentsui/Metrics.java +*/ +message DocsUIFileOperationCanceledReported { + optional android.stats.docsui.FileOperation file_op = 1; +} + +/** + * Logs startup time in milliseconds. + * + * Logged from: + * package/app/DocumentsUI/src/com/android/documentsui/Metrics.java + */ +message DocsUIStartupMsReported { + optional int32 startup_millis = 1; +} + +/** + * Logs the action that was started by user. + * + * Logged from: + * package/app/DocumentsUI/src/com/android/documentsui/Metrics.java + */ +message DocsUIUserActionReported { + optional android.stats.docsui.UserAction action = 1; +} + +/** + * Logs the invalid type when invalid scoped access is requested. + * + * Logged from: + * package/app/DocumentsUI/src/com/android/documentsui/ScopedAccessMetrics.java + */ +message DocsUIInvalidScopedAccessRequestReported { + optional android.stats.docsui.InvalidScopedAccess type = 1; +}
\ No newline at end of file diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp index f501574d6afc..7043d663eb2c 100644 --- a/cmds/statsd/src/external/StatsPuller.cpp +++ b/cmds/statsd/src/external/StatsPuller.cpp @@ -59,15 +59,21 @@ bool StatsPuller::Pull(const int64_t elapsedTimeNs, std::vector<std::shared_ptr< mLastPullTimeNs = elapsedTimeNs; int64_t pullStartTimeNs = getElapsedRealtimeNs(); bool ret = PullInternal(&mCachedData); + if (!ret) { + mCachedData.clear(); + return false; + } StatsdStats::getInstance().notePullTime(mTagId, getElapsedRealtimeNs() - pullStartTimeNs); for (const shared_ptr<LogEvent>& data : mCachedData) { data->setElapsedTimestampNs(elapsedTimeNs); data->setLogdWallClockTimestampNs(wallClockTimeNs); } - if (ret && mCachedData.size() > 0) { + + if (mCachedData.size() > 0) { mapAndMergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId); (*data) = mCachedData; } + StatsdStats::getInstance().notePullDelay(mTagId, getElapsedRealtimeNs() - elapsedTimeNs); return ret; } diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h index 22cb2f5c2175..cafd7979601a 100644 --- a/cmds/statsd/src/external/StatsPuller.h +++ b/cmds/statsd/src/external/StatsPuller.h @@ -39,6 +39,7 @@ public: // Pulls the data. The returned data will have elapsedTimeNs set as timeNs // and will have wallClockTimeNs set as current wall clock time. + // Return true if the pull is successful. bool Pull(const int64_t timeNs, std::vector<std::shared_ptr<LogEvent>>* data); // Clear cache immediately diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 5ca88142f152..14f2de0b1a48 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -49,6 +49,8 @@ const int FIELD_ID_TIME_BASE = 9; const int FIELD_ID_BUCKET_SIZE = 10; const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12; +const int FIELD_ID_IS_ACTIVE = 13; + // for CountMetricDataWrapper const int FIELD_ID_DATA = 1; // for CountMetricData @@ -151,10 +153,13 @@ void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, } else { flushIfNeededLocked(dumpTimeNs); } + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_IS_ACTIVE, isActiveLocked()); + + if (mPastBuckets.empty()) { return; } - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 35deffe5db97..7797bd98961d 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -48,6 +48,7 @@ const int FIELD_ID_TIME_BASE = 9; const int FIELD_ID_BUCKET_SIZE = 10; const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12; +const int FIELD_ID_IS_ACTIVE = 13; // for DurationMetricDataWrapper const int FIELD_ID_DATA = 1; // for DurationMetricData @@ -461,12 +462,14 @@ void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, } else { flushIfNeededLocked(dumpTimeNs); } + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_IS_ACTIVE, isActiveLocked()); + if (mPastBuckets.empty()) { VLOG(" Duration metric, empty return"); return; } - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index a18e406b74ca..31a4361f353d 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -44,6 +44,7 @@ namespace statsd { // for StatsLogReport const int FIELD_ID_ID = 1; const int FIELD_ID_EVENT_METRICS = 4; +const int FIELD_ID_IS_ACTIVE = 13; // for EventMetricDataWrapper const int FIELD_ID_DATA = 1; // for EventMetricData @@ -108,10 +109,11 @@ void EventMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, const bool erase_data, std::set<string> *str_set, ProtoOutputStream* protoOutput) { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_IS_ACTIVE, isActiveLocked()); if (mProto->size() <= 0) { return; } - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); size_t bufferSize = mProto->size(); VLOG("metric %lld dump report now... proto size: %zu ", diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 3a34743d55d6..03e42ce76460 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -49,6 +49,7 @@ const int FIELD_ID_TIME_BASE = 9; const int FIELD_ID_BUCKET_SIZE = 10; const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12; +const int FIELD_ID_IS_ACTIVE = 13; // for GaugeMetricDataWrapper const int FIELD_ID_DATA = 1; const int FIELD_ID_SKIPPED = 2; @@ -192,11 +193,13 @@ void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, flushIfNeededLocked(dumpTimeNs); } + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_IS_ACTIVE, isActiveLocked()); + if (mPastBuckets.empty()) { return; } - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 127cbbde1a3a..09e240929f08 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -223,6 +223,10 @@ protected: void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs); + inline bool isActiveLocked() const { + return mIsActive; + } + /** * Flushes the current bucket if the eventTime is after the current bucket's end time. This will also flush the current partial bucket in memory. diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index a34df8aabea2..1f22a6af3a3d 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -52,6 +52,7 @@ const int FIELD_ID_TIME_BASE = 9; const int FIELD_ID_BUCKET_SIZE = 10; const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11; const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12; +const int FIELD_ID_IS_ACTIVE = 13; // for ValueMetricDataWrapper const int FIELD_ID_DATA = 1; const int FIELD_ID_SKIPPED = 2; @@ -72,17 +73,15 @@ const int FIELD_ID_BUCKET_NUM = 4; const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5; const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; +const Value ZERO_LONG((int64_t)0); +const Value ZERO_DOUBLE((int64_t)0); + // ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently -ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, - const ValueMetric& metric, - const int conditionIndex, - const sp<ConditionWizard>& conditionWizard, - const int whatMatcherIndex, - const sp<EventMatcherWizard>& matcherWizard, - const int pullTagId, - const int64_t timeBaseNs, - const int64_t startTimeNs, - const sp<StatsPullerManager>& pullerManager) +ValueMetricProducer::ValueMetricProducer( + const ConfigKey& key, const ValueMetric& metric, const int conditionIndex, + const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex, + const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, const int64_t timeBaseNs, + const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager) : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, conditionWizard), mWhatMatcherIndex(whatMatcherIndex), mEventMatcherWizard(matcherWizard), @@ -102,7 +101,9 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, mAggregationType(metric.aggregation_type()), mUseDiff(metric.has_use_diff() ? metric.use_diff() : (mIsPulled ? true : false)), mValueDirection(metric.value_direction()), - mSkipZeroDiffOutput(metric.skip_zero_diff_output()) { + mSkipZeroDiffOutput(metric.skip_zero_diff_output()), + mUseZeroDefaultBase(metric.use_zero_default_base()), + mHasGlobalBase(false) { int64_t bucketSizeMills = 0; if (metric.has_bucket()) { bucketSizeMills = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()); @@ -190,10 +191,12 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, } else { flushIfNeededLocked(dumpTimeNs); } + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_IS_ACTIVE, isActiveLocked()); + if (mPastBuckets.empty() && mSkippedBuckets.empty()) { return; } - protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); // Fills the dimension path if not slicing by ALL. @@ -302,6 +305,15 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, } } +void ValueMetricProducer::resetBase() { + for (auto& slice : mCurrentSlicedBucket) { + for (auto& interval : slice.second) { + interval.hasBase = false; + } + } + mHasGlobalBase = false; +} + void ValueMetricProducer::onConditionChangedLocked(const bool condition, const int64_t eventTimeNs) { if (eventTimeNs < mCurrentBucketStartTimeNs) { @@ -317,13 +329,10 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition, pullAndMatchEventsLocked(eventTimeNs); } - // when condition change from true to false, clear diff base + // when condition change from true to false, clear diff base but don't + // reset other counters as we may accumulate more value in the bucket. if (mUseDiff && mCondition && !condition) { - for (auto& slice : mCurrentSlicedBucket) { - for (auto& interval : slice.second) { - interval.hasBase = false; - } - } + resetBase(); } mCondition = condition; @@ -332,15 +341,17 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition, void ValueMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) { vector<std::shared_ptr<LogEvent>> allData; if (mPullerManager->Pull(mPullTagId, timestampNs, &allData)) { - if (allData.size() == 0) { - return; - } for (const auto& data : allData) { if (mEventMatcherWizard->matchLogEvent( *data, mWhatMatcherIndex) == MatchingState::kMatched) { onMatchedLogEventLocked(mWhatMatcherIndex, *data); } } + mHasGlobalBase = true; + } else { + // for pulled data, every pull is needed. So we reset the base if any + // pull fails. + resetBase(); } } @@ -376,6 +387,7 @@ void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven onMatchedLogEventLocked(mWhatMatcherIndex, *data); } } + mHasGlobalBase = true; } else { VLOG("No need to commit data on condition false."); } @@ -420,6 +432,26 @@ bool ValueMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { return false; } +bool ValueMetricProducer::hitFullBucketGuardRailLocked(const MetricDimensionKey& newKey) { + // ===========GuardRail============== + // 1. Report the tuple count if the tuple count > soft limit + if (mCurrentFullBucket.find(newKey) != mCurrentFullBucket.end()) { + return false; + } + if (mCurrentFullBucket.size() > mDimensionSoftLimit - 1) { + size_t newTupleCount = mCurrentFullBucket.size() + 1; + // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. + if (newTupleCount > mDimensionHardLimit) { + ALOGE("ValueMetric %lld dropping data for full bucket dimension key %s", + (long long)mMetricId, + newKey.toString().c_str()); + return true; + } + } + + return false; +} + bool getDoubleOrLong(const LogEvent& event, const Matcher& matcher, Value& ret) { for (const FieldValue& value : event.getValues()) { if (value.mField.matches(matcher)) { @@ -484,13 +516,21 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIn VLOG("Failed to get value %d from event %s", i, event.ToString().c_str()); return; } + interval.seenNewData = true; if (mUseDiff) { - // no base. just update base and return. if (!interval.hasBase) { - interval.base = value; - interval.hasBase = true; - return; + if (mHasGlobalBase && mUseZeroDefaultBase) { + // The bucket has global base. This key does not. + // Optionally use zero as base. + interval.base = (value.type == LONG ? ZERO_LONG : ZERO_DOUBLE); + interval.hasBase = true; + } else { + // no base. just update base and return. + interval.base = value; + interval.hasBase = true; + return; + } } Value diff; switch (mValueDirection) { @@ -580,11 +620,7 @@ void ValueMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) { if (numBucketsForward > 1) { VLOG("Skipping forward %lld buckets", (long long)numBucketsForward); // take base again in future good bucket. - for (auto& slice : mCurrentSlicedBucket) { - for (auto& interval : slice.second) { - interval.hasBase = false; - } - } + resetBase(); } VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId, (long long)mCurrentBucketStartTimeNs); @@ -633,6 +669,9 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs) { // Accumulate partial buckets with current value and then send to anomaly tracker. if (mCurrentFullBucket.size() > 0) { for (const auto& slice : mCurrentSlicedBucket) { + if (hitFullBucketGuardRailLocked(slice.first)) { + continue; + } // TODO: fix this when anomaly can accept double values mCurrentFullBucket[slice.first] += slice.second[0].value.long_value; } @@ -664,11 +703,21 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs) { } } - // Reset counters - for (auto& slice : mCurrentSlicedBucket) { - for (auto& interval : slice.second) { + for (auto it = mCurrentSlicedBucket.begin(); it != mCurrentSlicedBucket.end();) { + bool obsolete = true; + for (auto& interval : it->second) { interval.hasValue = false; interval.sampleSize = 0; + if (interval.seenNewData) { + obsolete = false; + } + interval.seenNewData = false; + } + + if (obsolete) { + it = mCurrentSlicedBucket.erase(it); + } else { + it++; } } } diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 9fe84dcf93aa..4991af4cc75c 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -128,7 +128,9 @@ private: int sampleSize; // If this dimension has any non-tainted value. If not, don't report the // dimension. - bool hasValue; + bool hasValue = false; + // Whether new data is seen in the bucket. + bool seenNewData = false; } Interval; std::unordered_map<MetricDimensionKey, std::vector<Interval>> mCurrentSlicedBucket; @@ -146,8 +148,13 @@ private: // Util function to check whether the specified dimension hits the guardrail. bool hitGuardRailLocked(const MetricDimensionKey& newKey); + bool hitFullBucketGuardRailLocked(const MetricDimensionKey& newKey); + void pullAndMatchEventsLocked(const int64_t timestampNs); + // Reset diff base and mHasGlobalBase + void resetBase(); + static const size_t kBucketSize = sizeof(ValueBucket{}); const size_t mDimensionSoftLimit; @@ -164,6 +171,18 @@ private: const bool mSkipZeroDiffOutput; + // If true, use a zero value as base to compute the diff. + // This is used for new keys which are present in the new data but was not + // present in the base data. + // The default base will only be used if we have a global base. + const bool mUseZeroDefaultBase; + + // For pulled metrics, this is always set to true whenever a pull succeeds. + // It is set to false when a pull fails, or upon condition change to false. + // This is used to decide if we have the right base data to compute the + // diff against. + bool mHasGlobalBase; + FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsNoCondition); FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering); FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset); @@ -185,6 +204,9 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestFirstBucket); FRIEND_TEST(ValueMetricProducerTest, TestCalcPreviousBucketEndTime); FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput); + FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase); + FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures); + FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey); }; } // namespace statsd diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 32ee5af9ee21..a6f27c8aa535 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -219,6 +219,8 @@ message StatsLogReport { optional DimensionsValue dimensions_path_in_what = 11; optional DimensionsValue dimensions_path_in_condition = 12; + + optional bool is_active = 13; } message UidMapping { diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 61854a446e80..f485185cc9c2 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -274,6 +274,8 @@ message ValueMetric { optional bool use_diff = 12; + optional bool use_zero_default_base = 15 [default = false]; + enum ValueDirection { UNKNOWN = 0; INCREASING = 1; diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp index 2b0285b37473..370c36c75369 100644 --- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp @@ -119,7 +119,8 @@ TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit) { ConfigMetricsReport report = GetReports(service.mProcessor, start + 3); // Expect no metrics since the bucket has not finished yet. - EXPECT_EQ(0, report.metrics_size()); + EXPECT_EQ(1, report.metrics_size()); + EXPECT_EQ(0, report.metrics(0).count_metrics().data_size()); } TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp) { @@ -138,7 +139,8 @@ TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp) { service.mProcessor->OnLogEvent(CreateAppCrashEvent(100, start + 3).get()); ConfigMetricsReport report = GetReports(service.mProcessor, start + 4); - EXPECT_EQ(0, report.metrics_size()); + EXPECT_EQ(1, report.metrics_size()); + EXPECT_EQ(0, report.metrics(0).count_metrics().data_size()); } TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade) { diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp index 6d1317cb5dee..16be3d78f69b 100644 --- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp @@ -262,7 +262,8 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration1) // When using ProtoOutputStream, if nothing written to a sub msg, it won't be treated as // one. It was previsouly 1 because we had a fake onDumpReport which calls add_metric() by // itself. - EXPECT_EQ(0, reports.reports(0).metrics_size()); + EXPECT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_EQ(0, reports.reports(0).metrics(0).duration_metrics().data_size()); } TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration2) { diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 44aa00b3046c..55245033e762 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -1468,6 +1468,342 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { EXPECT_EQ(5, valueProducer.mPastBuckets.begin()->second.back().values[0].long_value); } +/* + * Tests zero default base. + */ +TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + metric.set_use_zero_default_base(true); + + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); + event->write(1); + event->write(3); + event->init(); + data->push_back(event); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, + logEventMatcherIndex, eventMatcherWizard, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + auto iter = valueProducer.mCurrentSlicedBucket.begin(); + auto& interval1 = iter->second[0]; + EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, interval1.hasBase); + EXPECT_EQ(3, interval1.base.long_value); + EXPECT_EQ(false, interval1.hasValue); + EXPECT_EQ(true, valueProducer.mHasGlobalBase); + EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); + vector<shared_ptr<LogEvent>> allData; + + allData.clear(); + shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); + event1->write(2); + event1->write(4); + event1->init(); + shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); + event2->write(1); + event2->write(11); + event2->init(); + allData.push_back(event1); + allData.push_back(event2); + + valueProducer.onDataPulled(allData); + EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size()); + EXPECT_EQ(true, interval1.hasBase); + EXPECT_EQ(11, interval1.base.long_value); + EXPECT_EQ(true, interval1.hasValue); + EXPECT_EQ(8, interval1.value.long_value); + + auto it = valueProducer.mCurrentSlicedBucket.begin(); + for (; it != valueProducer.mCurrentSlicedBucket.end(); it++) { + if (it != iter) { + break; + } + } + EXPECT_TRUE(it != iter); + auto& interval2 = it->second[0]; + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, interval2.hasBase); + EXPECT_EQ(4, interval2.base.long_value); + EXPECT_EQ(true, interval2.hasValue); + EXPECT_EQ(4, interval2.value.long_value); + EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); +} + +/* + * Tests using zero default base with failed pull. + */ +TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + metric.set_use_zero_default_base(true); + + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); + event->write(1); + event->write(3); + event->init(); + data->push_back(event); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, + logEventMatcherIndex, eventMatcherWizard, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + auto iter = valueProducer.mCurrentSlicedBucket.begin(); + auto& interval1 = iter->second[0]; + EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, interval1.hasBase); + EXPECT_EQ(3, interval1.base.long_value); + EXPECT_EQ(false, interval1.hasValue); + EXPECT_EQ(true, valueProducer.mHasGlobalBase); + EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); + vector<shared_ptr<LogEvent>> allData; + + allData.clear(); + shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); + event1->write(2); + event1->write(4); + event1->init(); + shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); + event2->write(1); + event2->write(11); + event2->init(); + allData.push_back(event1); + allData.push_back(event2); + + valueProducer.onDataPulled(allData); + EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size()); + EXPECT_EQ(true, interval1.hasBase); + EXPECT_EQ(11, interval1.base.long_value); + EXPECT_EQ(true, interval1.hasValue); + EXPECT_EQ(8, interval1.value.long_value); + + auto it = valueProducer.mCurrentSlicedBucket.begin(); + for (; it != valueProducer.mCurrentSlicedBucket.end(); it++) { + if (it != iter) { + break; + } + } + EXPECT_TRUE(it != iter); + auto& interval2 = it->second[0]; + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, interval2.hasBase); + EXPECT_EQ(4, interval2.base.long_value); + EXPECT_EQ(true, interval2.hasValue); + EXPECT_EQ(4, interval2.value.long_value); + EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); + + // next pull somehow did not happen, skip to end of bucket 3 + allData.clear(); + event1 = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); + event1->write(2); + event1->write(5); + event1->init(); + allData.push_back(event1); + valueProducer.onDataPulled(allData); + + EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size()); + EXPECT_EQ(true, interval2.hasBase); + EXPECT_EQ(5, interval2.base.long_value); + EXPECT_EQ(false, interval2.hasValue); + EXPECT_EQ(false, interval1.hasBase); + EXPECT_EQ(false, interval1.hasValue); + EXPECT_EQ(true, valueProducer.mHasGlobalBase); + EXPECT_EQ(2UL, valueProducer.mPastBuckets.size()); + + allData.clear(); + event1 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1); + event1->write(2); + event1->write(13); + event1->init(); + allData.push_back(event1); + event2 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1); + event2->write(1); + event2->write(5); + event2->init(); + allData.push_back(event2); + valueProducer.onDataPulled(allData); + + EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size()); + EXPECT_EQ(true, interval2.hasBase); + EXPECT_EQ(13, interval2.base.long_value); + EXPECT_EQ(true, interval2.hasValue); + EXPECT_EQ(8, interval2.value.long_value); + EXPECT_EQ(true, interval1.hasBase); + EXPECT_EQ(5, interval1.base.long_value); + EXPECT_EQ(true, interval1.hasValue); + EXPECT_EQ(5, interval1.value.long_value); + EXPECT_EQ(true, valueProducer.mHasGlobalBase); + EXPECT_EQ(2UL, valueProducer.mPastBuckets.size()); +} + +/* + * Tests trim unused dimension key if no new data is seen in an entire bucket. + */ +TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.mutable_dimensions_in_what()->set_field(tagId); + metric.mutable_dimensions_in_what()->add_child()->set_field(1); + + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs); + event->write(1); + event->write(3); + event->init(); + data->push_back(event); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, + logEventMatcherIndex, eventMatcherWizard, tagId, + bucketStartTimeNs, bucketStartTimeNs, pullerManager); + + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + auto iter = valueProducer.mCurrentSlicedBucket.begin(); + auto& interval1 = iter->second[0]; + EXPECT_EQ(1, iter->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, interval1.hasBase); + EXPECT_EQ(3, interval1.base.long_value); + EXPECT_EQ(false, interval1.hasValue); + EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); + vector<shared_ptr<LogEvent>> allData; + + allData.clear(); + shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); + event1->write(2); + event1->write(4); + event1->init(); + shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); + event2->write(1); + event2->write(11); + event2->init(); + allData.push_back(event1); + allData.push_back(event2); + + valueProducer.onDataPulled(allData); + EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size()); + EXPECT_EQ(true, interval1.hasBase); + EXPECT_EQ(11, interval1.base.long_value); + EXPECT_EQ(true, interval1.hasValue); + EXPECT_EQ(8, interval1.value.long_value); + EXPECT_TRUE(interval1.seenNewData); + + auto it = valueProducer.mCurrentSlicedBucket.begin(); + for (; it != valueProducer.mCurrentSlicedBucket.end(); it++) { + if (it != iter) { + break; + } + } + EXPECT_TRUE(it != iter); + auto& interval2 = it->second[0]; + EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); + EXPECT_EQ(true, interval2.hasBase); + EXPECT_EQ(4, interval2.base.long_value); + EXPECT_EQ(false, interval2.hasValue); + EXPECT_TRUE(interval2.seenNewData); + EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); + + // next pull somehow did not happen, skip to end of bucket 3 + allData.clear(); + event1 = make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1); + event1->write(2); + event1->write(5); + event1->init(); + allData.push_back(event1); + valueProducer.onDataPulled(allData); + + EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size()); + + EXPECT_EQ(false, interval1.hasBase); + EXPECT_EQ(false, interval1.hasValue); + EXPECT_EQ(8, interval1.value.long_value); + // on probation now + EXPECT_FALSE(interval1.seenNewData); + + EXPECT_EQ(true, interval2.hasBase); + EXPECT_EQ(5, interval2.base.long_value); + EXPECT_EQ(false, interval2.hasValue); + // back to good status + EXPECT_TRUE(interval2.seenNewData); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); + + allData.clear(); + event1 = make_shared<LogEvent>(tagId, bucket5StartTimeNs + 1); + event1->write(2); + event1->write(13); + event1->init(); + allData.push_back(event1); + valueProducer.onDataPulled(allData); + + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + EXPECT_EQ(true, interval2.hasBase); + EXPECT_EQ(13, interval2.base.long_value); + EXPECT_EQ(true, interval2.hasValue); + EXPECT_EQ(8, interval2.value.long_value); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index f3bf6e7b7231..a39f5e30bd51 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -46,6 +46,7 @@ public final class Telecom extends BaseCommand { private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled"; private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account"; private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account"; + private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app"; private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app"; private static final String COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP = "add-or-remove-call-companion-app"; @@ -68,6 +69,7 @@ public final class Telecom extends BaseCommand { "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n" + "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n" + "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n" + + "usage: telecom set-test-call-redirection-app <PACKAGE>\n" + "usage: telecom set-test-call-screening-app <PACKAGE>\n" + "usage: telecom set-test-auto-mode-app <PACKAGE>\n" + "usage: telecom add-or-remove-call-companion-app <PACKAGE> <1/0>\n" + @@ -120,6 +122,9 @@ public final class Telecom extends BaseCommand { case COMMAND_REGISTER_PHONE_ACCOUNT: runRegisterPhoneAccount(); break; + case COMMAND_SET_TEST_CALL_REDIRECTION_APP: + runSetTestCallRedirectionApp(); + break; case COMMAND_SET_TEST_CALL_SCREENING_APP: runSetTestCallScreeningApp(); break; @@ -189,6 +194,11 @@ public final class Telecom extends BaseCommand { System.out.println("Success - " + handle + " registered."); } + private void runSetTestCallRedirectionApp() throws RemoteException { + final String packageName = nextArg(); + mTelecomService.setTestDefaultCallRedirectionApp(packageName); + } + private void runSetTestCallScreeningApp() throws RemoteException { final String packageName = nextArg(); mTelecomService.setTestDefaultCallScreeningApp(packageName); diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index 579ef938fb1d..bacb991012fd 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -1639,10 +1639,6 @@ Landroid/widget/DigitalClock$FormatChangeObserver;-><init>(Landroid/widget/Digit Landroid/widget/QuickContactBadge$QueryHandler;-><init>(Landroid/widget/QuickContactBadge;Landroid/content/ContentResolver;)V Landroid/widget/RelativeLayout$DependencyGraph$Node;-><init>()V Landroid/widget/ScrollBarDrawable;-><init>()V -Lcom/android/i18n/phonenumbers/Phonenumber$PhoneNumber$CountryCodeSource;->values()[Lcom/android/i18n/phonenumbers/Phonenumber$PhoneNumber$CountryCodeSource; -Lcom/android/i18n/phonenumbers/PhoneNumberUtil$MatchType;->values()[Lcom/android/i18n/phonenumbers/PhoneNumberUtil$MatchType; -Lcom/android/i18n/phonenumbers/PhoneNumberUtil$PhoneNumberFormat;->values()[Lcom/android/i18n/phonenumbers/PhoneNumberUtil$PhoneNumberFormat; -Lcom/android/i18n/phonenumbers/PhoneNumberUtil$PhoneNumberType;->values()[Lcom/android/i18n/phonenumbers/PhoneNumberUtil$PhoneNumberType; Lcom/android/ims/ImsCall;->deflect(Ljava/lang/String;)V Lcom/android/ims/ImsCall;->isMultiparty()Z Lcom/android/ims/ImsCall;->reject(I)V @@ -1849,13 +1845,16 @@ Lcom/android/internal/location/GpsNetInitiatedHandler;->handleNiNotification(Lco Lcom/android/internal/location/GpsNetInitiatedHandler;->mIsHexInput:Z Lcom/android/internal/location/ILocationProvider$Stub;-><init>()V Lcom/android/internal/location/ILocationProvider$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/location/ILocationProvider; -Lcom/android/internal/location/ILocationProvider;->disable()V -Lcom/android/internal/location/ILocationProvider;->enable()V -Lcom/android/internal/location/ILocationProvider;->getProperties()Lcom/android/internal/location/ProviderProperties; Lcom/android/internal/location/ILocationProvider;->getStatus(Landroid/os/Bundle;)I Lcom/android/internal/location/ILocationProvider;->getStatusUpdateTime()J -Lcom/android/internal/location/ILocationProvider;->sendExtraCommand(Ljava/lang/String;Landroid/os/Bundle;)Z +Lcom/android/internal/location/ILocationProvider;->sendExtraCommand(Ljava/lang/String;Landroid/os/Bundle;)V +Lcom/android/internal/location/ILocationProvider;->setLocationProviderManager(Lcom/android/internal/location/ILocationProviderManager;)V Lcom/android/internal/location/ILocationProvider;->setRequest(Lcom/android/internal/location/ProviderRequest;Landroid/os/WorkSource;)V +Lcom/android/internal/location/ILocationProviderManager$Stub;-><init>()V +Lcom/android/internal/location/ILocationProviderManager$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/location/ILocationProviderManager; +Lcom/android/internal/location/ILocationProviderManager;->onReportLocation(Landroid/location/Location;)V +Lcom/android/internal/location/ILocationProviderManager;->onSetEnabled(Z)V +Lcom/android/internal/location/ILocationProviderManager;->onSetProperties(Lcom/android/internal/location/ProviderProperties;)V Lcom/android/internal/logging/MetricsLogger;-><init>()V Lcom/android/internal/net/LegacyVpnInfo;-><init>()V Lcom/android/internal/net/VpnConfig;-><init>()V @@ -2904,8 +2903,6 @@ Lcom/android/internal/telephony/dataconnection/DcFailCause;->USER_AUTHENTICATION Lcom/android/internal/telephony/dataconnection/DcTracker$RecoveryAction;->isAggressiveRecovery(I)Z Lcom/android/internal/telephony/dataconnection/DcTracker;->cancelReconnectAlarm(Lcom/android/internal/telephony/dataconnection/ApnContext;)V Lcom/android/internal/telephony/dataconnection/DcTracker;->cleanUpAllConnections(Ljava/lang/String;)V -Lcom/android/internal/telephony/dataconnection/DcTracker;->cleanUpAllConnections(ZLjava/lang/String;)Z -Lcom/android/internal/telephony/dataconnection/DcTracker;->cleanUpConnection(ZLcom/android/internal/telephony/dataconnection/ApnContext;)V Lcom/android/internal/telephony/dataconnection/DcTracker;->createAllApnList()V Lcom/android/internal/telephony/dataconnection/DcTracker;->getActiveApnTypes()[Ljava/lang/String; Lcom/android/internal/telephony/dataconnection/DcTracker;->getOverallState()Lcom/android/internal/telephony/DctConstants$State; @@ -2936,7 +2933,6 @@ Lcom/android/internal/telephony/dataconnection/DcTracker;->notifyDataConnection( Lcom/android/internal/telephony/dataconnection/DcTracker;->notifyOffApnsOfAvailability(Ljava/lang/String;)V Lcom/android/internal/telephony/dataconnection/DcTracker;->onActionIntentDataStallAlarm(Landroid/content/Intent;)V Lcom/android/internal/telephony/dataconnection/DcTracker;->onActionIntentProvisioningApnAlarm(Landroid/content/Intent;)V -Lcom/android/internal/telephony/dataconnection/DcTracker;->onCleanUpAllConnections(Ljava/lang/String;)V Lcom/android/internal/telephony/dataconnection/DcTracker;->onRecordsLoadedOrSubIdChanged()V Lcom/android/internal/telephony/dataconnection/DcTracker;->onSetUserDataEnabled(Z)V Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Lcom/android/internal/telephony/dataconnection/ApnContext;)Z @@ -2945,7 +2941,6 @@ Lcom/android/internal/telephony/dataconnection/DcTracker;->registerSettingsObser Lcom/android/internal/telephony/dataconnection/DcTracker;->resetPollStats()V Lcom/android/internal/telephony/dataconnection/DcTracker;->restartDataStallAlarm()V Lcom/android/internal/telephony/dataconnection/DcTracker;->setInitialAttachApn()V -Lcom/android/internal/telephony/dataconnection/DcTracker;->setInternalDataEnabled(ZLandroid/os/Message;)Z Lcom/android/internal/telephony/dataconnection/DcTracker;->setPreferredApn(I)V Lcom/android/internal/telephony/dataconnection/DcTracker;->setRadio(Z)V Lcom/android/internal/telephony/dataconnection/DcTracker;->setupDataOnConnectableApns(Ljava/lang/String;)V diff --git a/core/java/android/annotation/UnsupportedAppUsage.java b/core/java/android/annotation/UnsupportedAppUsage.java index 28145a06ecf2..ac3daaf638ad 100644 --- a/core/java/android/annotation/UnsupportedAppUsage.java +++ b/core/java/android/annotation/UnsupportedAppUsage.java @@ -18,29 +18,48 @@ package android.annotation; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.CLASS; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; /** - * Indicates that a class member, that is not part of the SDK, is used by apps. - * Since the member is not part of the SDK, such use is not supported. + * Indicates that this non-SDK interface is used by apps. A non-SDK interface is a + * class member (field or method) that is not part of the public SDK. Since the + * member is not part of the SDK, usage by apps is not supported. * - * <p>This annotation acts as a heads up that changing a given method or field + * <h2>If you are an Android App developer</h2> + * + * This annotation indicates that you may be able to access the member, but that + * this access is discouraged and not supported by Android. If there is a value + * for {@link #maxTargetSdk()} on the annotation, access will be restricted based + * on the {@code targetSdkVersion} value set in your manifest. + * + * <p>Fields and methods annotated with this are likely to be restricted, changed + * or removed in future Android releases. If you rely on these members for + * functionality that is not otherwise supported by Android, consider filing a + * <a href="http://g.co/dev/appcompat">feature request</a>. + * + * <h2>If you are an Android OS developer</h2> + * + * This annotation acts as a heads up that changing a given method or field * may affect apps, potentially breaking them when the next Android version is * released. In some cases, for members that are heavily used, this annotation * may imply restrictions on changes to the member. * * <p>This annotation also results in access to the member being permitted by the - * runtime, with a warning being generated in debug builds. + * runtime, with a warning being generated in debug builds. Which apps can access + * the member is determined by the value of {@link #maxTargetSdk()}. * * <p>For more details, see go/UnsupportedAppUsage. * * {@hide} */ @Retention(CLASS) -@Target({CONSTRUCTOR, METHOD, FIELD}) +@Target({CONSTRUCTOR, METHOD, FIELD, TYPE}) +@Repeatable(UnsupportedAppUsage.Container.class) public @interface UnsupportedAppUsage { /** @@ -90,4 +109,27 @@ public @interface UnsupportedAppUsage { * @return A dex API signature. */ String expectedSignature() default ""; + + /** + * The signature of an implicit (not present in the source) member that forms part of the + * hiddenapi. + * + * <p>Allows access to non-SDK API elements that are not represented in the input source to be + * managed. + * + * <p>This must only be used when applying the annotation to a type, using it in any other + * situation is an error. + * + * @return A dex API signature. + */ + String implicitMember() default ""; + + /** + * Container for {@link UnsupportedAppUsage} that allows it to be applied repeatedly to types. + */ + @Retention(CLASS) + @Target(TYPE) + @interface Container { + UnsupportedAppUsage[] value(); + } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index f3f065a27e6f..5e445d14a08b 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -121,8 +121,8 @@ import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager.AutofillClient; import android.view.autofill.AutofillPopupWindow; import android.view.autofill.IAutofillWindowPresenter; -import android.view.intelligence.ContentCaptureEvent; -import android.view.intelligence.ContentCaptureManager; +import android.view.contentcapture.ContentCaptureEvent; +import android.view.contentcapture.ContentCaptureManager; import android.widget.AdapterView; import android.widget.Toast; import android.widget.Toolbar; @@ -1723,15 +1723,7 @@ public class Activity extends ContextThemeWrapper if (mAutoFillResetNeeded) { if (!mAutoFillIgnoreFirstResumePause) { View focus = getCurrentFocus(); - // On Activity rotation situation (mRestoredFromBundle is true), - // we should not call on AutofillManager in onResume() - // since the next Layout pass will do that. - // However, there are both cases where Activity#getCurrentFocus() - // will return null (window not preserved) and not null (window IS - // preserved), so we need to explicitly check for mRestoredFromBundle - // here. - if (!mRestoredFromBundle && focus != null - && focus.canNotifyAutofillEnterExitEvent()) { + if (focus != null && focus.canNotifyAutofillEnterExitEvent()) { // TODO: in Activity killed/recreated case, i.e. SessionLifecycleTest# // testDatasetVisibleWhileAutofilledAppIsLifecycled: the View's initial // window visibility after recreation is INVISIBLE in onResume() and next frame @@ -6763,8 +6755,8 @@ public class Activity extends ContextThemeWrapper case "--autofill": dumpAutofillManager(prefix, writer); return; - case "--intelligence": - dumpIntelligenceManager(prefix, writer); + case "--contentcapture": + dumpContentCaptureManager(prefix, writer); return; } } @@ -6796,7 +6788,7 @@ public class Activity extends ContextThemeWrapper mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix); dumpAutofillManager(prefix, writer); - dumpIntelligenceManager(prefix, writer); + dumpContentCaptureManager(prefix, writer); ResourcesManager.getInstance().dump(prefix, writer); } @@ -6812,12 +6804,12 @@ public class Activity extends ContextThemeWrapper } } - void dumpIntelligenceManager(String prefix, PrintWriter writer) { - final ContentCaptureManager im = getContentCaptureManager(); - if (im != null) { - im.dump(prefix, writer); + void dumpContentCaptureManager(String prefix, PrintWriter writer) { + final ContentCaptureManager cm = getContentCaptureManager(); + if (cm != null) { + cm.dump(prefix, writer); } else { - writer.print(prefix); writer.println("No IntelligenceManager"); + writer.print(prefix); writer.println("No ContentCaptureManager"); } } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 1cf042fd12b4..84c778502393 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -3676,6 +3676,18 @@ public class ActivityManager { } /** + * Returns whether switching to provided user was successful. + * + * @param user the user to switch to. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public boolean switchUser(UserHandle user) { + return switchUser(user.getIdentifier()); + } + + /** * Logs out current current foreground user by switching to the system user and stopping the * user being switched from. * @hide diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index af3da0cbf5ee..f928501b7c1a 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -196,8 +196,26 @@ public abstract class ActivityManagerInternal { public abstract void updateOomAdj(); public abstract void updateCpuStats(); - public abstract void updateUsageStats( + + /** + * Update battery stats on activity usage. + * @param activity + * @param uid + * @param userId + * @param started + */ + public abstract void updateBatteryStats( ComponentName activity, int uid, int userId, boolean resumed); + + /** + * Update UsageStats of the activity. + * @param activity + * @param userId + * @param event + * @param appToken ActivityRecord's appToken. + */ + public abstract void updateActivityUsageStats( + ComponentName activity, int userId, int event, IBinder appToken); public abstract void updateForegroundTimeIfOnBattery( String packageName, int uid, long cpuTimeDiff); public abstract void sendForegroundProfileChanged(int userId); diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 6fdf7c8b4fac..4d8c8563ad7c 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -303,9 +303,7 @@ public class ActivityTaskManager { * @param stackId Id of stack to move the top activity to pinned stack. * @param bounds Bounds to use for pinned stack. * @return True if the top activity of stack was successfully moved to the pinned stack. - * @hide */ - @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) { try { @@ -318,9 +316,7 @@ public class ActivityTaskManager { /** * Start to enter lock task mode for given task by system(UI). * @param taskId Id of task to lock. - * @hide */ - @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void startSystemLockTaskMode(int taskId) { try { @@ -332,9 +328,7 @@ public class ActivityTaskManager { /** * Stop lock task mode by system(UI). - * @hide */ - @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void stopSystemLockTaskMode() { try { @@ -349,9 +343,7 @@ public class ActivityTaskManager { * @param taskId Id of the task to move. * @param stackId Id of the stack for task moving. * @param toTop Whether the given task should shown to top of stack. - * @hide */ - @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void moveTaskToStack(int taskId, int stackId, boolean toTop) { try { @@ -366,9 +358,7 @@ public class ActivityTaskManager { * @param stackId Id of the stack to resize. * @param bounds Bounds to resize the stack to or {@code null} for fullscreen. * @param animate Whether we should play an animation for resizing stack. - * @hide */ - @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeStack(int stackId, Rect bounds, boolean animate) { try { @@ -383,9 +373,7 @@ public class ActivityTaskManager { * Resize task to given bounds. * @param taskId Id of task to resize. * @param bounds Bounds to resize task. - * @hide */ - @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeTask(int taskId, Rect bounds) { try { @@ -399,9 +387,7 @@ public class ActivityTaskManager { * Resize docked stack & its task to given stack & task bounds. * @param stackBounds Bounds to resize stack. * @param taskBounds Bounds to resize task. - * @hide */ - @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeDockedStack(Rect stackBounds, Rect taskBounds) { try { @@ -413,9 +399,7 @@ public class ActivityTaskManager { /** * List all activity stacks information. - * @hide */ - @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public String listAllStacks() { final List<ActivityManager.StackInfo> stacks; @@ -438,7 +422,6 @@ public class ActivityTaskManager { * Clears launch params for the given package. * @param packageNames the names of the packages of which the launch params are to be cleared */ - @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void clearLaunchParamsForPackages(List<String> packageNames) { try { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 41166dd40e56..fe9b1ffb378f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -162,7 +162,6 @@ import com.android.org.conscrypt.OpenSSLSocketImpl; import com.android.org.conscrypt.TrustedCertificateStore; import com.android.server.am.MemInfoDumpProto; -import dalvik.system.BaseDexClassLoader; import dalvik.system.CloseGuard; import dalvik.system.VMDebug; import dalvik.system.VMRuntime; @@ -2282,6 +2281,15 @@ public final class ActivityThread extends ClientTransactionHandler { } } + /** + * Create the context instance base on system resources & display information which used for UI. + * @param displayId The ID of the display where the UI is shown. + * @see ContextImpl#createSystemUiContext(ContextImpl, int) + */ + public ContextImpl createSystemUiContext(int displayId) { + return ContextImpl.createSystemUiContext(getSystemUiContext(), displayId); + } + public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { synchronized (this) { getSystemContext().installSystemApplicationInfo(info, classLoader); @@ -5948,16 +5956,6 @@ public final class ActivityThread extends ClientTransactionHandler { HardwareRenderer.setIsolatedProcess(true); } - // If we use profiles, setup the dex reporter to notify package manager - // of any relevant dex loads. The idle maintenance job will use the information - // reported to optimize the loaded dex files. - // Note that we only need one global reporter per app. - // Make sure we do this before calling onCreate so that we can capture the - // complete application startup. - if (SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) { - BaseDexClassLoader.setReporter(DexLoadReporter.getInstance()); - } - // Install the Network Security Config Provider. This must happen before the application // code is loaded to prevent issues with instances of TLS objects being created before // the provider is installed. diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 3069be6b5714..6905cb5cea73 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -476,9 +476,11 @@ public class AppOpsManager { public static final int OP_READ_MEDIA_IMAGES = 85; /** @hide Write media of image type. */ public static final int OP_WRITE_MEDIA_IMAGES = 86; + /** @hide Has a legacy (non-isolated) view of storage. */ + public static final int OP_LEGACY_STORAGE = 87; /** @hide */ @UnsupportedAppUsage - public static final int _NUM_OP = 87; + public static final int _NUM_OP = 88; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -745,6 +747,8 @@ public class AppOpsManager { public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images"; /** @hide Write media of image type. */ public static final String OPSTR_WRITE_MEDIA_IMAGES = "android:write_media_images"; + /** @hide Has a legacy (non-isolated) view of storage. */ + public static final String OPSTR_LEGACY_STORAGE = "android:legacy_storage"; // Warning: If an permission is added here it also has to be added to // com.android.packageinstaller.permission.utils.EventLogger @@ -903,6 +907,7 @@ public class AppOpsManager { OP_WRITE_MEDIA_VIDEO, // WRITE_MEDIA_VIDEO OP_READ_MEDIA_IMAGES, // READ_MEDIA_IMAGES OP_WRITE_MEDIA_IMAGES, // WRITE_MEDIA_IMAGES + OP_LEGACY_STORAGE, // LEGACY_STORAGE }; /** @@ -996,6 +1001,7 @@ public class AppOpsManager { OPSTR_WRITE_MEDIA_VIDEO, OPSTR_READ_MEDIA_IMAGES, OPSTR_WRITE_MEDIA_IMAGES, + OPSTR_LEGACY_STORAGE, }; /** @@ -1090,6 +1096,7 @@ public class AppOpsManager { "WRITE_MEDIA_VIDEO", "READ_MEDIA_IMAGES", "WRITE_MEDIA_IMAGES", + "LEGACY_STORAGE", }; /** @@ -1185,6 +1192,7 @@ public class AppOpsManager { null, // no permission for OP_WRITE_MEDIA_VIDEO Manifest.permission.READ_MEDIA_IMAGES, null, // no permission for OP_WRITE_MEDIA_IMAGES + null, // no permission for OP_LEGACY_STORAGE }; /** @@ -1280,6 +1288,7 @@ public class AppOpsManager { null, // WRITE_MEDIA_VIDEO null, // READ_MEDIA_IMAGES null, // WRITE_MEDIA_IMAGES + null, // LEGACY_STORAGE }; /** @@ -1374,6 +1383,7 @@ public class AppOpsManager { false, // WRITE_MEDIA_VIDEO false, // READ_MEDIA_IMAGES false, // WRITE_MEDIA_IMAGES + false, // LEGACY_STORAGE }; /** @@ -1467,6 +1477,7 @@ public class AppOpsManager { AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_VIDEO AppOpsManager.MODE_ALLOWED, // READ_MEDIA_IMAGES AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_IMAGES + AppOpsManager.MODE_DEFAULT, // LEGACY_STORAGE }; /** @@ -1564,6 +1575,7 @@ public class AppOpsManager { false, // WRITE_MEDIA_VIDEO false, // READ_MEDIA_IMAGES false, // WRITE_MEDIA_IMAGES + false, // LEGACY_STORAGE }; /** @@ -2490,30 +2502,6 @@ public class AppOpsManager { } /** - * Retrieve current operation state for all applications. - * - * @param ops The set of operations you are interested in, or null if you want all of them. - * @hide - */ - @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) - @SystemApi - public List<AppOpsManager.PackageOps> getPackagesForOpStrs(String[] ops) { - if (ops == null) { - return getPackagesForOps(null); - } - final int[] opCodes = new int[ops.length]; - for (int i = 0; i < ops.length; ++i) { - final Integer opCode = sOpStrToOp.get(ops[i]); - if (opCode == null) { - opCodes[i] = OP_NONE; - } else { - opCodes[i] = opCode; - } - } - return getPackagesForOps(opCodes); - } - - /** * Retrieve current operation state for one application. * * @param uid The uid of the application of interest. diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 7312b2c8163e..67d9ad6e93c6 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2974,6 +2974,15 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public String getWellbeingPackageName() { + try { + return mPM.getWellbeingPackageName(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override public boolean isPackageStateProtected(String packageName, int userId) { try { return mPM.isPackageStateProtected(packageName, userId); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index c7a9d99fe927..92cdb20c7f4f 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2407,16 +2407,28 @@ class ContextImpl extends Context { /** * System Context to be used for UI. This Context has resources that can be themed. * Make sure that the created system UI context shares the same LoadedApk as the system context. + * @param systemContext The system context which created by + * {@link #createSystemContext(ActivityThread)}. + * @param displayId The ID of the display where the UI is shown. */ - static ContextImpl createSystemUiContext(ContextImpl systemContext) { + static ContextImpl createSystemUiContext(ContextImpl systemContext, int displayId) { final LoadedApk packageInfo = systemContext.mPackageInfo; ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null, null, null, 0, null); - context.setResources(createResources(null, packageInfo, null, Display.DEFAULT_DISPLAY, null, + context.setResources(createResources(null, packageInfo, null, displayId, null, packageInfo.getCompatibilityInfo())); + context.updateDisplay(displayId); return context; } + /** + * The overloaded method of {@link #createSystemUiContext(ContextImpl, int)}. + * Uses {@Code Display.DEFAULT_DISPLAY} as the target display. + */ + static ContextImpl createSystemUiContext(ContextImpl systemContext) { + return createSystemUiContext(systemContext, Display.DEFAULT_DISPLAY); + } + @UnsupportedAppUsage static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 759763b4199a..d46dbedfe8c2 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -57,6 +57,7 @@ import android.view.DisplayAdjustments; import com.android.internal.util.ArrayUtils; +import dalvik.system.BaseDexClassLoader; import dalvik.system.VMRuntime; import java.io.File; @@ -949,6 +950,15 @@ public final class LoadedApk { if (!SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) { return; } + + // If we use profiles, setup the dex reporter to notify package manager + // of any relevant dex loads. The idle maintenance job will use the information + // reported to optimize the loaded dex files. + // Note that we only need one global reporter per app. + // Make sure we do this before invoking app code for the first time so that we + // can capture the complete application startup. + BaseDexClassLoader.setReporter(DexLoadReporter.getInstance()); + // Only set up profile support if the loaded apk has the same uid as the // current process. // Currently, we do not support profiling across different apps. diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index aa1b5af9b112..b9d590741263 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -4770,14 +4770,16 @@ public class Notification implements Parcelable TemplateBindResult result) { boolean largeIconShown = bindLargeIcon(contentView, p); boolean replyIconShown = bindReplyIcon(contentView, p); + boolean iconContainerVisible = largeIconShown || replyIconShown; contentView.setViewVisibility(R.id.right_icon_container, - largeIconShown || replyIconShown ? View.VISIBLE : View.GONE); + iconContainerVisible ? View.VISIBLE : View.GONE); int marginEnd = calculateMarginEnd(largeIconShown, replyIconShown); contentView.setViewLayoutMarginEnd(R.id.line1, marginEnd); contentView.setViewLayoutMarginEnd(R.id.text, marginEnd); contentView.setViewLayoutMarginEnd(R.id.progress, marginEnd); if (result != null) { result.setIconMarginEnd(marginEnd); + result.setRightIconContainerVisible(iconContainerVisible); } } @@ -6777,7 +6779,8 @@ public class Notification implements Parcelable mBuilder.setTextViewColorSecondary(contentView, R.id.big_text, p); contentView.setViewVisibility(R.id.big_text, TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE); - contentView.setBoolean(R.id.big_text, "setHasImage", mBuilder.mN.hasLargeIcon()); + contentView.setBoolean(R.id.big_text, "setHasImage", + result.isRightIconContainerVisible()); return contentView; } @@ -9914,6 +9917,7 @@ public class Notification implements Parcelable */ private static class TemplateBindResult { int mIconMarginEnd; + boolean mRightIconContainerVisible; /** * Get the margin end that needs to be added to any fields that may overlap @@ -9923,9 +9927,21 @@ public class Notification implements Parcelable return mIconMarginEnd; } + /** + * Is the icon container visible on the right size because of the reply button or the + * right icon. + */ + public boolean isRightIconContainerVisible() { + return mRightIconContainerVisible; + } + public void setIconMarginEnd(int iconMarginEnd) { this.mIconMarginEnd = iconMarginEnd; } + + public void setRightIconContainerVisible(boolean iconContainerVisible) { + mRightIconContainerVisible = iconContainerVisible; + } } private static class StandardTemplateParams { diff --git a/core/java/android/app/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java index 6747004e8186..7cc3dab5866c 100644 --- a/core/java/android/app/RecoverableSecurityException.java +++ b/core/java/android/app/RecoverableSecurityException.java @@ -16,15 +16,13 @@ package android.app; -import android.content.ContentProvider; -import android.content.ContentResolver; +import android.annotation.NonNull; import android.content.Context; -import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import com.android.internal.util.Preconditions; +import java.util.Objects; /** * Specialization of {@link SecurityException} that contains additional @@ -35,18 +33,11 @@ import com.android.internal.util.Preconditions; * authentication credentials, or granting access. * <p> * If the receiving app is actively involved with the user, it should present - * the contained recovery details to help the user make forward progress. The - * {@link #showAsDialog(Activity)} and - * {@link #showAsNotification(Context, String)} methods are provided as a - * convenience, but receiving apps are encouraged to use - * {@link #getUserMessage()} and {@link #getUserAction()} to integrate in a more - * natural way if relevant. + * the contained recovery details to help the user make forward progress. * <p class="note"> * Note: legacy code that receives this exception may treat it as a general * {@link SecurityException}, and thus there is no guarantee that the messages * contained will be shown to the end user. - * - * @hide */ public final class RecoverableSecurityException extends SecurityException implements Parcelable { private static final String TAG = "RecoverableSecurityException"; @@ -78,53 +69,28 @@ public final class RecoverableSecurityException extends SecurityException implem * apps that observe {@link Activity#RESULT_OK} may choose to * immediately retry their operation. */ - public RecoverableSecurityException(Throwable cause, CharSequence userMessage, - RemoteAction userAction) { + public RecoverableSecurityException(@NonNull Throwable cause, @NonNull CharSequence userMessage, + @NonNull RemoteAction userAction) { super(cause.getMessage()); - mUserMessage = Preconditions.checkNotNull(userMessage); - mUserAction = Preconditions.checkNotNull(userAction); - } - - /** {@hide} */ - @Deprecated - public RecoverableSecurityException(Throwable cause, CharSequence userMessage, - CharSequence userActionTitle, PendingIntent userAction) { - this(cause, userMessage, - new RemoteAction( - Icon.createWithResource("android", - com.android.internal.R.drawable.ic_restart), - userActionTitle, userActionTitle, userAction)); + mUserMessage = Objects.requireNonNull(userMessage); + mUserAction = Objects.requireNonNull(userAction); } /** * Return short message describing the issue for end user audiences, which * may be shown in a notification or dialog. */ - public CharSequence getUserMessage() { + public @NonNull CharSequence getUserMessage() { return mUserMessage; } /** * Return primary action that will initiate the recovery. */ - public RemoteAction getUserAction() { + public @NonNull RemoteAction getUserAction() { return mUserAction; } - /** @removed */ - @Deprecated - public void showAsNotification(Context context) { - final NotificationManager nm = context.getSystemService(NotificationManager.class); - - // Create a channel per-sender, since we don't want one poorly behaved - // remote app to cause all of our notifications to be blocked - final String channelId = TAG + "_" + mUserAction.getActionIntent().getCreatorUid(); - nm.createNotificationChannel(new NotificationChannel(channelId, TAG, - NotificationManager.IMPORTANCE_DEFAULT)); - - showAsNotification(context, channelId); - } - /** * Convenience method that will show a very simple notification populated * with the details from this exception. @@ -142,6 +108,7 @@ public final class RecoverableSecurityException extends SecurityException implem * @param channelId the {@link NotificationChannel} to use, which must have * been already created using * {@link NotificationManager#createNotificationChannel}. + * @hide */ public void showAsNotification(Context context, String channelId) { final NotificationManager nm = context.getSystemService(NotificationManager.class); @@ -167,6 +134,8 @@ public final class RecoverableSecurityException extends SecurityException implem * <p> * This method will only display the most recent exception from any single * remote UID; dialogs from older exceptions will always be replaced. + * + * @hide */ public void showAsDialog(Activity activity) { final LocalDialog dialog = new LocalDialog(); diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java index 85fe99d95969..ebbf317ad05d 100644 --- a/core/java/android/app/RemoteInput.java +++ b/core/java/android/app/RemoteInput.java @@ -93,6 +93,22 @@ public final class RemoteInput implements Parcelable { /** The user selected one of the choices from {@link #getChoices}. */ public static final int SOURCE_CHOICE = 1; + /** @hide */ + @IntDef(prefix = {"EDIT_CHOICES_BEFORE_SENDING_"}, + value = {EDIT_CHOICES_BEFORE_SENDING_AUTO, EDIT_CHOICES_BEFORE_SENDING_DISABLED, + EDIT_CHOICES_BEFORE_SENDING_ENABLED}) + @Retention(RetentionPolicy.SOURCE) + public @interface EditChoicesBeforeSending {} + + /** The platform will determine whether choices will be edited before being sent to the app. */ + public static final int EDIT_CHOICES_BEFORE_SENDING_AUTO = 0; + + /** Tapping on a choice should send the input immediately, without letting the user edit it. */ + public static final int EDIT_CHOICES_BEFORE_SENDING_DISABLED = 1; + + /** Tapping on a choice should let the user edit the input before it is sent to the app. */ + public static final int EDIT_CHOICES_BEFORE_SENDING_ENABLED = 2; + // Flags bitwise-ored to mFlags private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1; @@ -103,17 +119,25 @@ public final class RemoteInput implements Parcelable { private final CharSequence mLabel; private final CharSequence[] mChoices; private final int mFlags; + @EditChoicesBeforeSending private final int mEditChoicesBeforeSending; private final Bundle mExtras; private final ArraySet<String> mAllowedDataTypes; private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices, - int flags, Bundle extras, ArraySet<String> allowedDataTypes) { + int flags, int editChoicesBeforeSending, Bundle extras, + ArraySet<String> allowedDataTypes) { this.mResultKey = resultKey; this.mLabel = label; this.mChoices = choices; this.mFlags = flags; + this.mEditChoicesBeforeSending = editChoicesBeforeSending; this.mExtras = extras; this.mAllowedDataTypes = allowedDataTypes; + if (getEditChoicesBeforeSending() == EDIT_CHOICES_BEFORE_SENDING_ENABLED + && !getAllowFreeFormInput()) { + throw new IllegalArgumentException( + "setEditChoicesBeforeSending requires setAllowFreeFormInput"); + } } /** @@ -169,6 +193,15 @@ public final class RemoteInput implements Parcelable { } /** + * Gets whether tapping on a choice should let the user edit the input before it is sent to the + * app. + */ + @EditChoicesBeforeSending + public int getEditChoicesBeforeSending() { + return mEditChoicesBeforeSending; + } + + /** * Get additional metadata carried around with this remote input. */ public Bundle getExtras() { @@ -185,6 +218,8 @@ public final class RemoteInput implements Parcelable { private CharSequence mLabel; private CharSequence[] mChoices; private int mFlags = DEFAULT_FLAGS; + @EditChoicesBeforeSending + private int mEditChoicesBeforeSending = EDIT_CHOICES_BEFORE_SENDING_AUTO; /** * Create a builder object for {@link RemoteInput} objects. @@ -269,7 +304,20 @@ public final class RemoteInput implements Parcelable { */ @NonNull public Builder setAllowFreeFormInput(boolean allowFreeFormTextInput) { - setFlag(mFlags, allowFreeFormTextInput); + setFlag(FLAG_ALLOW_FREE_FORM_INPUT, allowFreeFormTextInput); + return this; + } + + /** + * Specifies whether tapping on a choice should let the user edit the input before it is + * sent to the app. The default is {@link #EDIT_CHOICES_BEFORE_SENDING_AUTO}. + * + * It cannot be used if {@link #setAllowFreeFormInput} has been set to false. + */ + @NonNull + public Builder setEditChoicesBeforeSending( + @EditChoicesBeforeSending int editChoicesBeforeSending) { + mEditChoicesBeforeSending = editChoicesBeforeSending; return this; } @@ -312,8 +360,8 @@ public final class RemoteInput implements Parcelable { */ @NonNull public RemoteInput build() { - return new RemoteInput( - mResultKey, mLabel, mChoices, mFlags, mExtras, mAllowedDataTypes); + return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mEditChoicesBeforeSending, + mExtras, mAllowedDataTypes); } } @@ -322,6 +370,7 @@ public final class RemoteInput implements Parcelable { mLabel = in.readCharSequence(); mChoices = in.readCharSequenceArray(); mFlags = in.readInt(); + mEditChoicesBeforeSending = in.readInt(); mExtras = in.readBundle(); mAllowedDataTypes = (ArraySet<String>) in.readArraySet(null); } @@ -507,6 +556,7 @@ public final class RemoteInput implements Parcelable { out.writeCharSequence(mLabel); out.writeCharSequenceArray(mChoices); out.writeInt(mFlags); + out.writeInt(mEditChoicesBeforeSending); out.writeBundle(mExtras); out.writeArraySet(mAllowedDataTypes); } diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 16f6bdaa4313..5fa8526b5a24 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -685,6 +685,13 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * the permission {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use * this API.</p> * + * <p>To use this API, apps targeting API {@link android.os.Build.VERSION_CODES#Q} or later must + * specify the foreground service type using attribute + * {@link android.R.attr#foregroundServiceType} in service element of manifest file, otherwise + * a SecurityException is thrown when this API is called. Apps targeting API older than + * {@link android.os.Build.VERSION_CODES#Q} do not need to specify the foreground service type + * </p> + * * @param id The identifier for this notification as per * {@link NotificationManager#notify(int, Notification) * NotificationManager.notify(int, Notification)}; must not be 0. @@ -700,7 +707,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac } catch (RemoteException ex) { } } - + /** * Synonym for {@link #stopForeground(int)}. * @param removeNotification If true, the {@link #STOP_FOREGROUND_REMOVE} flag diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 0404e80df9e2..43e183665435 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -165,9 +165,9 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.CaptioningManager; import android.view.autofill.AutofillManager; import android.view.autofill.IAutoFillManager; +import android.view.contentcapture.ContentCaptureManager; +import android.view.contentcapture.IContentCaptureManager; import android.view.inputmethod.InputMethodManager; -import android.view.intelligence.ContentCaptureManager; -import android.view.intelligence.IIntelligenceManager; import android.view.textclassifier.TextClassificationManager; import android.view.textservice.TextServicesManager; @@ -1070,7 +1070,7 @@ final class SystemServiceRegistry { if (outerContext.isContentCaptureSupported()) { IBinder b = ServiceManager .getService(Context.CONTENT_CAPTURE_MANAGER_SERVICE); - IIntelligenceManager service = IIntelligenceManager.Stub.asInterface(b); + IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(b); return new ContentCaptureManager(outerContext, service); } return null; diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index dc2f9838785c..31521a369a4c 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -331,7 +331,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { writeTo.flush(); } } catch (IOException ioe) { - throw new RuntimeException("Error while reading/writing ", ioe); + Log.w(TAG, "Error while reading/writing to streams"); } finally { IoUtils.closeQuietly(readFrom); IoUtils.closeQuietly(writeTo); diff --git a/core/java/android/app/admin/DelegatedAdminReceiver.java b/core/java/android/app/admin/DelegatedAdminReceiver.java new file mode 100644 index 000000000000..dc8dfdff2e00 --- /dev/null +++ b/core/java/android/app/admin/DelegatedAdminReceiver.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import static android.app.admin.DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS; +import static android.app.admin.DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE; +import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS; +import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID; +import static android.app.admin.DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URI; +import static android.app.admin.DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT; +import static android.app.admin.DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.security.KeyChain; +import android.util.Log; + +/** + * Base class for delegated apps to handle callbacks related to their delegated capabilities. + * + * <p>Delegated apps are apps that receive additional capabilities from the profile owner or + * device owner apps. Some of these capabilities involve the framework calling into the apps. + * To receive these callbacks, delegated apps should subclass this class and override the + * appropriate methods here. The subclassed receiver needs to be published in the app's + * manifest, with appropriate intent filters to mark which callbacks the receiver is interested + * in. An app can have multiple receivers as long as they listen for disjoint set of callbacks. + * For the manifest definitions, it must be protected by the + * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission to ensure only + * the system can trigger these callbacks. + * + * <p>The callback methods happen on the main thread of the process. Thus long running + * operations must be done on another thread. Note that because a receiver + * is done once returning from its receive function, such long-running operations + * should probably be done in a {@link Service}. + * + * @see DevicePolicyManager#setDelegatedScopes + * @see DeviceAdminReceiver + */ +public class DelegatedAdminReceiver extends BroadcastReceiver { + private static final String TAG = "DelegatedAdminReceiver"; + + /** + * Allows this receiver to select the alias for a private key and certificate pair for + * authentication. If this method returns null, the default {@link android.app.Activity} will + * be shown that lets the user pick a private key and certificate pair. + * + * <p> This callback is only applicable if the delegated app has + * {@link DevicePolicyManager#DELEGATION_CERT_SELECTION} capability. Additionally, it must + * declare an intent fitler for {@link #ACTION_CHOOSE_PRIVATE_KEY_ALIAS} in the receiver's + * manifest in order to receive this callback. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param uid The uid asking for the private key and certificate pair. + * @param uri The URI to authenticate, may be null. + * @param alias The alias preselected by the client, or null. + * @return The private key alias to return and grant access to. + * @see KeyChain#choosePrivateKeyAlias + */ + public String onChoosePrivateKeyAlias(Context context, Intent intent, int uid, Uri uri, + String alias) { + return null; + } + + /** + * Called each time a new batch of network logs can be retrieved. This callback method will only + * ever be called when network logging is enabled. The logs can only be retrieved while network + * logging is enabled. + * + * <p>If a secondary user or profile is created, this callback won't be received until all users + * become affiliated again (even if network logging is enabled). It will also no longer be + * possible to retrieve the network logs batch with the most recent {@code batchToken} provided + * by this callback. See {@link DevicePolicyManager#setAffiliationIds}. + * + * <p> This callback is only applicable if the delegated app has + * {@link DevicePolicyManager#DELEGATION_NETWORK_LOGGING} capability. Additionally, it must + * declare an intent fitler for {@link #ACTION_NETWORK_LOGS_AVAILABLE} in the receiver's + * manifest in order to receive this callback. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @param batchToken The token representing the current batch of network logs. + * @param networkLogsCount The total count of events in the current batch of network logs. + * @see DevicePolicyManager#retrieveNetworkLogs + */ + public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken, + int networkLogsCount) { + } + + /** + * Intercept delegated device administrator broadcasts. Implementations should not override + * this method; implement the convenience callbacks for each action instead. + */ + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (ACTION_CHOOSE_PRIVATE_KEY_ALIAS.equals(action)) { + int uid = intent.getIntExtra(EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, -1); + Uri uri = intent.getParcelableExtra(EXTRA_CHOOSE_PRIVATE_KEY_URI); + String alias = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_ALIAS); + String chosenAlias = onChoosePrivateKeyAlias(context, intent, uid, uri, alias); + setResultData(chosenAlias); + } else if (ACTION_NETWORK_LOGS_AVAILABLE.equals(action)) { + long batchToken = intent.getLongExtra(EXTRA_NETWORK_LOGS_TOKEN, -1); + int networkLogsCount = intent.getIntExtra(EXTRA_NETWORK_LOGS_COUNT, 0); + onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount); + } else { + Log.w(TAG, "Unhandled broadcast: " + action); + } + } +} diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 1c9477d08cb3..5a7124e1fdc6 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -23,7 +23,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SystemApi; import android.app.Service; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -297,7 +296,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { /** * Broadcast action: notify that a new batch of network logs is ready to be collected. * @see DeviceAdminReceiver#onNetworkLogsAvailable - * @hide + * @see DelegatedAdminReceiver#onNetworkLogsAvailable */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @BroadcastBehavior(explicitOnly = true) @@ -426,7 +425,11 @@ public class DeviceAdminReceiver extends BroadcastReceiver { */ public static final int BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE = 1; - /** @hide */ + /** + * Broadcast action: notify that some app is attempting to choose a KeyChain key. + * @see DeviceAdminReceiver#onChoosePrivateKeyAlias + * @see DelegatedAdminReceiver#onChoosePrivateKeyAlias + */ public static final String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS"; @@ -755,7 +758,6 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @deprecated Do not use */ @Deprecated - @SystemApi public void onReadyForUserInitialization(Context context, Intent intent) { } diff --git a/core/java/android/app/admin/DevicePolicyEventLogger.java b/core/java/android/app/admin/DevicePolicyEventLogger.java index f39a5f4480a8..c89d86865358 100644 --- a/core/java/android/app/admin/DevicePolicyEventLogger.java +++ b/core/java/android/app/admin/DevicePolicyEventLogger.java @@ -16,6 +16,8 @@ package android.app.admin; +import android.annotation.Nullable; +import android.content.ComponentName; import android.stats.devicepolicy.nano.StringList; import android.util.StatsLog; @@ -34,7 +36,7 @@ import com.android.internal.util.Preconditions; * * DevicePolicyEventLogger * .createEvent(DevicePolicyEnums.USER_RESTRICTION_CHANGED) - * .setAdminPackageName(who) + * .setAdmin(who) * .setString(key) * .setBoolean(enabledFromThisOwner) * .write(); @@ -170,12 +172,20 @@ public final class DevicePolicyEventLogger { /** * Sets the package name of the admin application. */ - public DevicePolicyEventLogger setAdminPackageName(String packageName) { + public DevicePolicyEventLogger setAdmin(@Nullable String packageName) { mAdminPackageName = packageName; return this; } /** + * Retrieves the package name of the admin application from the {@link ComponentName}. + */ + public DevicePolicyEventLogger setAdmin(@Nullable ComponentName componentName) { + mAdminPackageName = (componentName != null ? componentName.getPackageName() : null); + return this; + } + + /** * Returns the package name of the admin application. */ @VisibleForTesting diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 3a9728413151..670f8db7c588 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -16,6 +16,7 @@ package android.app.admin; +import android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; import android.annotation.IntDef; @@ -49,6 +50,8 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.graphics.Bitmap; +import android.net.NetworkUtils; +import android.net.PrivateDnsConnectivityChecker; import android.net.ProxyInfo; import android.net.Uri; import android.os.Binder; @@ -64,6 +67,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.UserManager.UserOperationException; import android.os.UserManager.UserOperationResult; +import android.provider.CalendarContract; import android.provider.ContactsContract.Directory; import android.provider.Settings; import android.security.AttestedKeyPair; @@ -79,6 +83,7 @@ import android.security.keystore.StrongBoxUnavailableException; import android.service.restrictions.RestrictionsReceiver; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; @@ -1121,6 +1126,64 @@ public class DevicePolicyManager { "android.app.extra.PROVISIONING_USE_MOBILE_DATA"; /** + * A String extra holding the provisioning trigger. It could be one of + * {@link #PROVISIONING_TRIGGER_CLOUD_ENROLLMENT}, {@link #PROVISIONING_TRIGGER_QR_CODE}, + * {@link #PROVISIONING_TRIGGER_PERSISTENT_DEVICE_OWNER} or {@link + * #PROVISIONING_TRIGGER_UNSPECIFIED}. + * + * <p>Use in an intent with action {@link + * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. + * @hide + */ + @SystemApi + public static final String EXTRA_PROVISIONING_TRIGGER = + "android.app.extra.PROVISIONING_TRIGGER"; + + /** + * A value for {@link #EXTRA_PROVISIONING_TRIGGER} indicating that the provisioning + * trigger has not been specified. + * @see #PROVISIONING_TRIGGER_CLOUD_ENROLLMENT + * @see #PROVISIONING_TRIGGER_QR_CODE + * @see #PROVISIONING_TRIGGER_PERSISTENT_DEVICE_OWNER + * @hide + */ + @SystemApi + public static final int PROVISIONING_TRIGGER_UNSPECIFIED = 0; + + /** + * A value for {@link #EXTRA_PROVISIONING_TRIGGER} indicating that the provisioning + * trigger is cloud enrollment. + * @see #PROVISIONING_TRIGGER_QR_CODE + * @see #PROVISIONING_TRIGGER_PERSISTENT_DEVICE_OWNER + * @see #PROVISIONING_TRIGGER_UNSPECIFIED + * @hide + */ + @SystemApi + public static final int PROVISIONING_TRIGGER_CLOUD_ENROLLMENT = 1; + + /** + * A value for {@link #EXTRA_PROVISIONING_TRIGGER} indicating that the provisioning + * trigger is the QR code scanner. + * @see #PROVISIONING_TRIGGER_CLOUD_ENROLLMENT + * @see #PROVISIONING_TRIGGER_PERSISTENT_DEVICE_OWNER + * @see #PROVISIONING_TRIGGER_UNSPECIFIED + * @hide + */ + @SystemApi + public static final int PROVISIONING_TRIGGER_QR_CODE = 2; + + /** + * A value for {@link #EXTRA_PROVISIONING_TRIGGER} indicating that the provisioning + * trigger is persistent device owner enrollment. + * @see #PROVISIONING_TRIGGER_CLOUD_ENROLLMENT + * @see #PROVISIONING_TRIGGER_QR_CODE + * @see #PROVISIONING_TRIGGER_UNSPECIFIED + * @hide + */ + @SystemApi + public static final int PROVISIONING_TRIGGER_PERSISTENT_DEVICE_OWNER = 3; + + /** * This MIME type is used for starting the device owner provisioning. * * <p>During device owner provisioning a device admin app is set as the owner of the device. @@ -1320,6 +1383,73 @@ public class DevicePolicyManager { = "android.app.action.SET_NEW_PASSWORD"; /** + * Constant for {@link #getPasswordComplexity()}: no password. + * + * <p>Note that these complexity constants are ordered so that higher values are more complex. + */ + public static final int PASSWORD_COMPLEXITY_NONE = 0; + + /** + * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following: + * <ul> + * <li>pattern + * <li>PIN with repeating (4444) or ordered (1234, 4321, 2468) sequences + * </ul> + * + * <p>Note that these complexity constants are ordered so that higher values are more complex. + * + * @see #PASSWORD_QUALITY_SOMETHING + * @see #PASSWORD_QUALITY_NUMERIC + */ + public static final int PASSWORD_COMPLEXITY_LOW = 0x10000; + + /** + * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following: + * <ul> + * <li>PIN with <b>no</b> repeating (4444) or ordered (1234, 4321, 2468) sequences, length at + * least 4 + * <li>alphabetic, length at least 4 + * <li>alphanumeric, length at least 4 + * </ul> + * + * <p>Note that these complexity constants are ordered so that higher values are more complex. + * + * @see #PASSWORD_QUALITY_NUMERIC_COMPLEX + * @see #PASSWORD_QUALITY_ALPHABETIC + * @see #PASSWORD_QUALITY_ALPHANUMERIC + */ + public static final int PASSWORD_COMPLEXITY_MEDIUM = 0x30000; + + /** + * Constant for {@link #getPasswordComplexity()}: password satisfies one of the following: + * <ul> + * <li>PIN with <b>no</b> repeating (4444) or ordered (1234, 4321, 2468) sequences, length at + * least 4 + * <li>alphabetic, length at least 6 + * <li>alphanumeric, length at least 6 + * </ul> + * + * <p>Note that these complexity constants are ordered so that higher values are more complex. + * + * @see #PASSWORD_QUALITY_NUMERIC_COMPLEX + * @see #PASSWORD_QUALITY_ALPHABETIC + * @see #PASSWORD_QUALITY_ALPHANUMERIC + */ + public static final int PASSWORD_COMPLEXITY_HIGH = 0x50000; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"PASSWORD_COMPLEXITY_"}, value = { + PASSWORD_COMPLEXITY_NONE, + PASSWORD_COMPLEXITY_LOW, + PASSWORD_COMPLEXITY_MEDIUM, + PASSWORD_COMPLEXITY_HIGH, + }) + public @interface PasswordComplexity {} + + /** * Activity action: have the user enter a new password for the parent profile. * If the intent is launched from within a managed profile, this will trigger * entering a new password for the parent of the profile. In all other cases @@ -1485,12 +1615,46 @@ public class DevicePolicyManager { /** * Delegation of management of uninstalled packages. This scope grants access to the - * {@code #setKeepUninstalledPackages} and {@code #getKeepUninstalledPackages} APIs. + * {@link #setKeepUninstalledPackages} and {@link #getKeepUninstalledPackages} APIs. */ public static final String DELEGATION_KEEP_UNINSTALLED_PACKAGES = "delegation-keep-uninstalled-packages"; /** + * 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. + * 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. + */ + public static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging"; + + /** + * Grants access to selection of KeyChain certificates on behalf of requesting apps. + * Once granted the app will start receiving + * DelegatedAdminReceiver.onChoosePrivateKeyAlias. The caller (PO/DO) will + * no longer receive {@link DeviceAdminReceiver#onChoosePrivateKeyAlias()}. + * There can be at most one app that has this delegation. + * If another app already had delegated certificate selection access, + * it will lose the delegation when a new app is delegated. + * + * <p> Can be granted by Device Owner or Profile Owner. + */ + public static final String DELEGATION_CERT_SELECTION = "delegation-cert-selection"; + + + /** + * Delegation of silent APK installation via {@link android.content.pm.PackageInstaller} APIs. + * + * <p> Can only be delegated by Device Owner. + */ + public static final String DELEGATION_PACKAGE_INSTALLATION = "delegation-package-installation"; + + /** * No management for current user in-effect. This is the default. * @hide */ @@ -1974,6 +2138,35 @@ public class DevicePolicyManager { public @interface InstallUpdateCallbackErrorConstants {} /** + * The selected mode has been set successfully. If the mode is + * {@code PRIVATE_DNS_MODE_PROVIDER_HOSTNAME} then it implies the supplied host is valid + * and reachable. + */ + public static final int PRIVATE_DNS_SET_SUCCESS = 0; + + /** + * If the {@code privateDnsHost} provided was of a valid hostname but that host was found + * to not support DNS-over-TLS. + */ + public static final int PRIVATE_DNS_SET_ERROR_HOST_NOT_SERVING = 1; + + /** + * General failure to set the Private DNS mode, not due to one of the reasons listed above. + */ + public static final int PRIVATE_DNS_SET_ERROR_FAILURE_SETTING = 2; + + /** + * @hide + */ + @IntDef(prefix = {"PRIVATE_DNS_SET_"}, value = { + PRIVATE_DNS_SET_SUCCESS, + PRIVATE_DNS_SET_ERROR_HOST_NOT_SERVING, + PRIVATE_DNS_SET_ERROR_FAILURE_SETTING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SetPrivateDnsModeResultConstants {} + + /** * Return true if the given administrator component is currently active (enabled) in the system. * * @param admin The administrator component to check for. @@ -2981,6 +3174,33 @@ public class DevicePolicyManager { } /** + * Returns how complex the current user's screen lock is. + * + * <p>Note that when called from a profile which uses an unified challenge with its parent, the + * screen lock complexity of the parent will be returned. However, this API does not support + * explicitly querying the parent profile screen lock complexity via {@link + * #getParentProfileInstance}. + * + * @throws IllegalStateException if the user is not unlocked. + * @throws SecurityException if the calling application does not have the permission + * {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY} + */ + @PasswordComplexity + @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY) + public int getPasswordComplexity() { + throwIfParentInstance("getPasswordComplexity"); + if (mService == null) { + return PASSWORD_COMPLEXITY_NONE; + } + + try { + return mService.getPasswordComplexity(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * When called by a profile owner of a managed profile returns true if the profile uses unified * challenge with its parent user. * @@ -9148,7 +9368,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner to control the network logging feature. + * Called by a device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to + * control the network logging feature. * * <p> Network logs contain DNS lookup and connect() library call events. The following library * functions are recorded while network logging is active: @@ -9185,16 +9406,17 @@ public class DevicePolicyManager { * all users to become affiliated. Therefore it's recommended that affiliation ids are set for * new users as soon as possible after provisioning via {@link #setAffiliationIds}. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or + * {@code null} if called by a delegated app. * @param enabled whether network logging should be enabled or not. * @throws SecurityException if {@code admin} is not a device owner. * @see #setAffiliationIds * @see #retrieveNetworkLogs */ - public void setNetworkLoggingEnabled(@NonNull ComponentName admin, boolean enabled) { + public void setNetworkLoggingEnabled(@Nullable ComponentName admin, boolean enabled) { throwIfParentInstance("setNetworkLoggingEnabled"); try { - mService.setNetworkLoggingEnabled(admin, enabled); + mService.setNetworkLoggingEnabled(admin, mContext.getPackageName(), enabled); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -9204,7 +9426,8 @@ public class DevicePolicyManager { * Return whether network logging is enabled by a device owner. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only - * be {@code null} if the caller has MANAGE_USERS permission. + * be {@code null} if the caller is a delegated app with {@link #DELEGATION_NETWORK_LOGGING} + * or has MANAGE_USERS permission. * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner and caller has * no MANAGE_USERS permission @@ -9212,14 +9435,15 @@ public class DevicePolicyManager { public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) { throwIfParentInstance("isNetworkLoggingEnabled"); try { - return mService.isNetworkLoggingEnabled(admin); + return mService.isNetworkLoggingEnabled(admin, mContext.getPackageName()); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** - * Called by device owner to retrieve the most recent batch of network logging events. + * Called by device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to retrieve + * the most recent batch of network logging events. * A device owner has to provide a batchToken provided as part of * {@link DeviceAdminReceiver#onNetworkLogsAvailable} callback. If the token doesn't match the * token of the most recent available batch of logs, {@code null} will be returned. @@ -9238,7 +9462,8 @@ public class DevicePolicyManager { * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See * {@link DevicePolicyManager#setAffiliationIds}. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or + * {@code null} if called by a delegated app. * @param batchToken A token of the batch to retrieve * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns * {@code null} if the batch represented by batchToken is no longer available or if @@ -9248,11 +9473,11 @@ public class DevicePolicyManager { * @see #setAffiliationIds * @see DeviceAdminReceiver#onNetworkLogsAvailable */ - public @Nullable List<NetworkEvent> retrieveNetworkLogs(@NonNull ComponentName admin, + public @Nullable List<NetworkEvent> retrieveNetworkLogs(@Nullable ComponentName admin, long batchToken) { throwIfParentInstance("retrieveNetworkLogs"); try { - return mService.retrieveNetworkLogs(admin, batchToken); + return mService.retrieveNetworkLogs(admin, mContext.getPackageName(), batchToken); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -9839,6 +10064,16 @@ public class DevicePolicyManager { * Sets the global Private DNS mode and host to be used. * May only be called by the device owner. * + * <p>Note that in case a Private DNS resolver is specified, the method is blocking as it + * will perform a connectivity check to the resolver, to ensure it is valid. Because of that, + * the method should not be called on any thread that relates to user interaction, such as the + * UI thread. + * + * <p>In case a VPN is used in conjunction with Private DNS resolver, the Private DNS resolver + * must be reachable both from within and outside the VPN. Otherwise, the device may lose + * the ability to resolve hostnames as system traffic to the resolver may not go through the + * VPN. + * * @param admin which {@link DeviceAdminReceiver} this request is associated with. * @param mode Which mode to set - either {@code PRIVATE_DNS_MODE_OPPORTUNISTIC} or * {@code PRIVATE_DNS_MODE_PROVIDER_HOSTNAME}. @@ -9848,6 +10083,9 @@ public class DevicePolicyManager { * @param privateDnsHost The hostname of a server that implements DNS over TLS (RFC7858), if * {@code PRIVATE_DNS_MODE_PROVIDER_HOSTNAME} was specified as the mode, * null otherwise. + * + * @return One of the values in {@link SetPrivateDnsModeResultConstants}. + * * @throws IllegalArgumentException in the following cases: if a {@code privateDnsHost} was * provided but the mode was not {@code PRIVATE_DNS_MODE_PROVIDER_HOSTNAME}, if the mode * specified was {@code PRIVATE_DNS_MODE_PROVIDER_HOSTNAME} but {@code privateDnsHost} does @@ -9855,15 +10093,23 @@ public class DevicePolicyManager { * * @throws SecurityException if the caller is not the device owner. */ - public void setGlobalPrivateDns(@NonNull ComponentName admin, + public int setGlobalPrivateDns(@NonNull ComponentName admin, @PrivateDnsMode int mode, @Nullable String privateDnsHost) { throwIfParentInstance("setGlobalPrivateDns"); + if (mService == null) { - return; + return PRIVATE_DNS_SET_ERROR_FAILURE_SETTING; + } + + if (mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME && !TextUtils.isEmpty(privateDnsHost) + && NetworkUtils.isWeaklyValidatedHostname(privateDnsHost)) { + if (!PrivateDnsConnectivityChecker.canConnectToPrivateDnsServer(privateDnsHost)) { + return PRIVATE_DNS_SET_ERROR_HOST_NOT_SERVING; + } } try { - mService.setGlobalPrivateDns(admin, mode, privateDnsHost); + return mService.setGlobalPrivateDns(admin, mode, privateDnsHost); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -10128,4 +10374,102 @@ public class DevicePolicyManager { } return Collections.emptySet(); } + + /** + * Returns whether the device is being used as a managed kiosk, as defined in the CDD. As of + * this release, these requirements are as follows: + * <ul> + * <li>The device is in Lock Task (therefore there is also a Device Owner app on the + * device)</li> + * <li>The Lock Task feature {@link DevicePolicyManager#LOCK_TASK_FEATURE_SYSTEM_INFO} is + * not enabled, so the system info in the status bar is not visible</li> + * <li>The device does not have a secure lock screen (e.g. it has no lock screen or has + * swipe-to-unlock)</li> + * <li>The device is not in the middle of an ephemeral user session</li> + * </ul> + * + * <p>Publicly-accessible dedicated devices don't have the same privacy model as + * personally-used devices. In particular, user consent popups don't make sense as a barrier to + * accessing persistent data on these devices since the user giving consent and the user whose + * data is on the device are unlikely to be the same. These consent popups prevent the true + * remote management of these devices. + * + * <p>This condition is not sufficient to cover APIs that would access data that only lives for + * the duration of the user's session, since the user has an expectation of privacy in these + * conditions that more closely resembles use of a personal device. In those cases, see {@link + * #isUnattendedManagedKiosk()}. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public boolean isManagedKiosk() { + throwIfParentInstance("isManagedKiosk"); + if (mService != null) { + try { + return mService.isManagedKiosk(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Returns whether the device is being used as an unattended managed kiosk, as defined in the + * CDD. As of this release, these requirements are as follows: + * <ul> + * <li>The device is being used as a managed kiosk, as defined in the CDD and verified at + * {@link #isManagedKiosk()}</li> + * <li>The device has not received user input for at least 30 minutes</li> + * </ul> + * + * <p>See {@link #isManagedKiosk()} for context. This is a stronger requirement that also + * ensures that the device hasn't been interacted with recently, making it an appropriate check + * for privacy-sensitive APIs that wouldn't be appropriate during an active user session. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public boolean isUnattendedManagedKiosk() { + throwIfParentInstance("isUnattendedManagedKiosk"); + if (mService != null) { + try { + return mService.isUnattendedManagedKiosk(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Starts an activity to view calendar events in the managed profile. + * + * @param eventId the id of the event to be viewed. + * @param start the start time of the event. + * @param end the end time of the event. + * @param allDay if the event is an all-day event. + * @param flags flags to be set for the intent + * @return {@code true} if the activity is started successfully. {@code false} otherwise. + * + * @see CalendarContract#startViewCalendarEventInManagedProfile(Context, String, long, long, + * long, boolean, int) + * + * @hide + */ + public boolean startViewCalendarEventInManagedProfile(long eventId, long start, long end, + boolean allDay, int flags) { + throwIfParentInstance("startViewCalendarEventInManagedProfile"); + if (mService != null) { + try { + return mService.startViewCalendarEventInManagedProfile(mContext.getPackageName(), + eventId, start, end, allDay, flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index fcf74ee301d8..568becfcdd1a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -82,6 +82,7 @@ interface IDevicePolicyManager { boolean isActivePasswordSufficient(int userHandle, boolean parent); boolean isProfileActivePasswordSufficientForParent(int userHandle); + int getPasswordComplexity(); boolean isUsingUnifiedPassword(in ComponentName admin); int getCurrentFailedPasswordAttempts(int userHandle, boolean parent); int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent); @@ -366,9 +367,9 @@ interface IDevicePolicyManager { void setBackupServiceEnabled(in ComponentName admin, boolean enabled); boolean isBackupServiceEnabled(in ComponentName admin); - void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled); - boolean isNetworkLoggingEnabled(in ComponentName admin); - List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin, long batchToken); + void setNetworkLoggingEnabled(in ComponentName admin, in String packageName, boolean enabled); + boolean isNetworkLoggingEnabled(in ComponentName admin, in String packageName); + List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin, in String packageName, long batchToken); boolean bindDeviceAdminServiceAsUser(in ComponentName admin, IApplicationThread caller, IBinder token, in Intent service, @@ -415,7 +416,7 @@ interface IDevicePolicyManager { boolean isMeteredDataDisabledPackageForUser(in ComponentName admin, String packageName, int userId); - void setGlobalPrivateDns(in ComponentName admin, int mode, in String privateDnsHost); + int setGlobalPrivateDns(in ComponentName admin, int mode, in String privateDnsHost); int getGlobalPrivateDnsMode(in ComponentName admin); String getGlobalPrivateDnsHost(in ComponentName admin); @@ -428,4 +429,9 @@ interface IDevicePolicyManager { List<String> getCrossProfileCalendarPackages(in ComponentName admin); boolean isPackageAllowedToAccessCalendarForUser(String packageName, int userHandle); List<String> getCrossProfileCalendarPackagesForUser(int userHandle); + + boolean isManagedKiosk(); + boolean isUnattendedManagedKiosk(); + + boolean startViewCalendarEventInManagedProfile(String packageName, long eventId, long start, long end, boolean allDay, int flags); } diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java index 5fee853275fb..8b41755f6dec 100644 --- a/core/java/android/app/admin/PasswordMetrics.java +++ b/core/java/android/app/admin/PasswordMetrics.java @@ -16,8 +16,14 @@ package android.app.admin; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; + import android.annotation.IntDef; import android.annotation.NonNull; +import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.os.Parcel; import android.os.Parcelable; @@ -35,6 +41,8 @@ public class PasswordMetrics implements Parcelable { // consider it a complex PIN/password. public static final int MAX_ALLOWED_SEQUENCE = 3; + // TODO(b/120536847): refactor isActivePasswordSufficient logic so that the actual password + // quality is not overwritten public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; public int length = 0; public int letters = 0; @@ -46,6 +54,10 @@ public class PasswordMetrics implements Parcelable { public PasswordMetrics() {} + public PasswordMetrics(int quality) { + this.quality = quality; + } + public PasswordMetrics(int quality, int length) { this.quality = quality; this.length = length; @@ -173,6 +185,15 @@ public class PasswordMetrics implements Parcelable { && this.nonLetter == o.nonLetter; } + private boolean satisfiesBucket(PasswordMetrics... bucket) { + for (PasswordMetrics metrics : bucket) { + if (this.quality == metrics.quality) { + return this.length >= metrics.length; + } + } + return false; + } + /* * Returns the maximum length of a sequential characters. A sequence is defined as * monotonically increasing characters with a constant interval or the same character repeated. @@ -254,4 +275,99 @@ public class PasswordMetrics implements Parcelable { return 0; } } + + /** Determines the {@link PasswordComplexity} of this {@link PasswordMetrics}. */ + @PasswordComplexity + public int determineComplexity() { + for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) { + if (satisfiesBucket(bucket.getMetrics())) { + return bucket.mComplexityLevel; + } + } + return PASSWORD_COMPLEXITY_NONE; + } + + /** + * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}. + */ + public static class PasswordComplexityBucket { + /** + * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of + * {@link PasswordMetrics}. + */ + private static final PasswordComplexityBucket HIGH = + new PasswordComplexityBucket( + PASSWORD_COMPLEXITY_HIGH, + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 6), + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6), + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ + 8)); + + /** + * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of + * {@link PasswordMetrics}. + */ + private static final PasswordComplexityBucket MEDIUM = + new PasswordComplexityBucket( + PASSWORD_COMPLEXITY_MEDIUM, + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4), + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4), + new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ + 4)); + + /** + * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_LOW} in terms of + * {@link PasswordMetrics}. + */ + private static final PasswordComplexityBucket LOW = + new PasswordComplexityBucket( + PASSWORD_COMPLEXITY_LOW, + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)); + + /** + * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}. + */ + private static final PasswordComplexityBucket NONE = + new PasswordComplexityBucket(PASSWORD_COMPLEXITY_NONE, new PasswordMetrics()); + + /** Array containing all buckets from high to low. */ + private static final PasswordComplexityBucket[] BUCKETS = + new PasswordComplexityBucket[] {HIGH, MEDIUM, LOW}; + + @PasswordComplexity + private final int mComplexityLevel; + private final PasswordMetrics[] mMetrics; + + private PasswordComplexityBucket(@PasswordComplexity int complexityLevel, + PasswordMetrics... metrics) { + this.mComplexityLevel = complexityLevel; + this.mMetrics = metrics; + } + + /** Returns the {@link PasswordMetrics} that meet the min requirements of this bucket. */ + public PasswordMetrics[] getMetrics() { + return mMetrics; + } + + /** Returns the bucket that {@code complexityLevel} represents. */ + public static PasswordComplexityBucket complexityLevelToBucket( + @PasswordComplexity int complexityLevel) { + for (PasswordComplexityBucket bucket : BUCKETS) { + if (bucket.mComplexityLevel == complexityLevel) { + return bucket; + } + } + return NONE; + } + } } diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java index bcd5f6c7497f..dd7284540fef 100644 --- a/core/java/android/app/admin/SystemUpdatePolicy.java +++ b/core/java/android/app/admin/SystemUpdatePolicy.java @@ -689,13 +689,11 @@ public final class SystemUpdatePolicy implements Parcelable { mFreezePeriods.stream().map(n -> n.toString()).collect(Collectors.joining(","))); } - @SystemApi @Override public int describeContents() { return 0; } - @SystemApi @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mPolicyType); @@ -712,7 +710,6 @@ public final class SystemUpdatePolicy implements Parcelable { } } - @SystemApi public static final Parcelable.Creator<SystemUpdatePolicy> CREATOR = new Parcelable.Creator<SystemUpdatePolicy>() { diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index 3e2074837b8e..0afb98f1ed84 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -91,6 +91,15 @@ interface IBackupManager { * at some point in the future. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which backup service should be enabled/disabled. + */ + void setBackupEnabledForUser(int userId, boolean isEnabled); + + /** + * {@link android.app.backup.IBackupManager.setBackupEnabledForUser} for the calling user id. */ void setBackupEnabled(boolean isEnabled); @@ -120,6 +129,15 @@ interface IBackupManager { * Report whether the backup mechanism is currently enabled. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the backup service status should be reported. + */ + boolean isBackupEnabledForUser(int userId); + + /** + * {@link android.app.backup.IBackupManager.isBackupEnabledForUser} for the calling user id. */ boolean isBackupEnabled(); @@ -149,6 +167,15 @@ interface IBackupManager { * method. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which an immediate backup should be scheduled. + */ + void backupNowForUser(int userId); + + /** + * {@link android.app.backup.IBackupManager.backupNowForUser} for the calling user id. */ void backupNow(); @@ -432,6 +459,12 @@ interface IBackupManager { * <p>If this method returns zero (meaning success), the OS will attempt to backup all provided * packages using the remote transport. * + * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which an immediate backup should be requested. + * @param observer The {@link BackupObserver} to receive callbacks during the backup * operation. * @@ -442,12 +475,29 @@ interface IBackupManager { * * @return Zero on success; nonzero on error. */ + int requestBackupForUser(int userId, in String[] packages, IBackupObserver observer, + IBackupManagerMonitor monitor, int flags); + + /** + * {@link android.app.backup.IBackupManager.requestBackupForUser} for the calling user id. + */ int requestBackup(in String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor, int flags); /** * Cancel all running backups. After this call returns, no currently running backups will * interact with the selected transport. + * + * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which backups should be cancelled. + */ + void cancelBackupsForUser(int userId); + + /** + * {@link android.app.backup.IBackupManager.cancelBackups} for the calling user id. */ void cancelBackups(); } diff --git a/core/java/android/app/backup/RestoreSession.java b/core/java/android/app/backup/RestoreSession.java index 2e0f940331c5..79925ec2d47a 100644 --- a/core/java/android/app/backup/RestoreSession.java +++ b/core/java/android/app/backup/RestoreSession.java @@ -143,8 +143,6 @@ public class RestoreSession { * @param packages The set of packages for which to attempt a restore. Regardless of * the contents of the actual back-end dataset named by {@code token}, only * applications mentioned in this list will have their data restored. - * - * @hide */ public int restoreSome(long token, RestoreObserver observer, BackupManagerMonitor monitor, String[] packages) { @@ -181,8 +179,6 @@ public class RestoreSession { * @param packages The set of packages for which to attempt a restore. Regardless of * the contents of the actual back-end dataset named by {@code token}, only * applications mentioned in this list will have their data restored. - * - * @hide */ public int restoreSome(long token, RestoreObserver observer, String[] packages) { return restoreSome(token, observer, null, packages); diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index 556ffa24368f..e84517dc6942 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -200,12 +200,23 @@ public class JobInfo implements Parcelable { public static final int PRIORITY_SYNC_INITIALIZATION = 20; /** - * Value of {@link #getPriority} for a foreground app (overrides the supplied + * Value of {@link #getPriority} for a BFGS app (overrides the supplied + * JobInfo priority if it is smaller). + * @hide + */ + public static final int PRIORITY_BOUND_FOREGROUND_SERVICE = 30; + + /** @hide For backward compatibility. */ + @UnsupportedAppUsage + public static final int PRIORITY_FOREGROUND_APP = PRIORITY_BOUND_FOREGROUND_SERVICE; + + /** + * Value of {@link #getPriority} for a FG service app (overrides the supplied * JobInfo priority if it is smaller). * @hide */ @UnsupportedAppUsage - public static final int PRIORITY_FOREGROUND_APP = 30; + public static final int PRIORITY_FOREGROUND_SERVICE = 35; /** * Value of {@link #getPriority} for the current top app (overrides the supplied @@ -1593,4 +1604,29 @@ public class JobInfo implements Parcelable { return new JobInfo(this); } } + + /** + * Convert a priority integer into a human readable string for debugging. + * @hide + */ + public static String getPriorityString(int priority) { + switch (priority) { + case PRIORITY_DEFAULT: + return PRIORITY_DEFAULT + " [DEFAULT]"; + case PRIORITY_SYNC_EXPEDITED: + return PRIORITY_SYNC_EXPEDITED + " [SYNC_EXPEDITED]"; + case PRIORITY_SYNC_INITIALIZATION: + return PRIORITY_SYNC_INITIALIZATION + " [SYNC_INITIALIZATION]"; + case PRIORITY_BOUND_FOREGROUND_SERVICE: + return PRIORITY_BOUND_FOREGROUND_SERVICE + " [BFGS_APP]"; + case PRIORITY_FOREGROUND_SERVICE: + return PRIORITY_FOREGROUND_SERVICE + " [FGS_APP]"; + case PRIORITY_TOP_APP: + return PRIORITY_TOP_APP + " [TOP_APP]"; + + // PRIORITY_ADJ_* are adjustments and not used as real priorities. + // No need to convert to strings. + } + return priority + " [UNKNOWN]"; + } } diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java index 578a9aee539a..3cc56ae4b7e6 100644 --- a/core/java/android/app/job/JobParameters.java +++ b/core/java/android/app/job/JobParameters.java @@ -47,6 +47,8 @@ public class JobParameters implements Parcelable { public static final int REASON_TIMEOUT = JobProtoEnums.STOP_REASON_TIMEOUT; // 3. /** @hide */ public static final int REASON_DEVICE_IDLE = JobProtoEnums.STOP_REASON_DEVICE_IDLE; // 4. + /** @hide */ + public static final int REASON_DEVICE_THERMAL = JobProtoEnums.STOP_REASON_DEVICE_THERMAL; // 5. /** @hide */ public static String getReasonName(int reason) { diff --git a/core/java/android/service/intelligence/InteractionSessionId.aidl b/core/java/android/app/role/IOnRoleHoldersChangedListener.aidl index a5392b684a11..6cf961fad2c4 100644 --- a/core/java/android/service/intelligence/InteractionSessionId.aidl +++ b/core/java/android/app/role/IOnRoleHoldersChangedListener.aidl @@ -1,11 +1,11 @@ -/** - * Copyright (c) 2018, The Android Open Source Project +/* + * Copyright (C) 2018 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,6 +14,12 @@ * limitations under the License. */ -package android.service.intelligence; +package android.app.role; + +/** + * @hide + */ +oneway interface IOnRoleHoldersChangedListener { -parcelable InteractionSessionId; + void onRoleHoldersChanged(String roleName, int userId); +} diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl index 3ca8ec04f54e..4ce0f318bad5 100644 --- a/core/java/android/app/role/IRoleManager.aidl +++ b/core/java/android/app/role/IRoleManager.aidl @@ -16,6 +16,7 @@ package android.app.role; +import android.app.role.IOnRoleHoldersChangedListener; import android.app.role.IRoleManagerCallback; /** @@ -37,6 +38,11 @@ interface IRoleManager { void clearRoleHoldersAsUser(in String roleName, int userId, in IRoleManagerCallback callback); + void addOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener, int userId); + + void removeOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener, + int userId); + void setRoleNamesFromController(in List<String> roleNames); boolean addRoleHolderFromController(in String roleName, in String packageName); diff --git a/core/java/android/app/role/OnRoleHoldersChangedListener.java b/core/java/android/app/role/OnRoleHoldersChangedListener.java new file mode 100644 index 000000000000..5958debc86dd --- /dev/null +++ b/core/java/android/app/role/OnRoleHoldersChangedListener.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.role; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.UserHandle; + +/** + * Listener for role holder changes. + * + * @hide + */ +@SystemApi +public interface OnRoleHoldersChangedListener { + + /** + * Called when the holders of roles are changed. + * + * @param roleName the name of the role whose holders are changed + * @param user the user for this role holder change + */ + void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user); +} diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java index 7cb245adb609..5d101ab479ac 100644 --- a/core/java/android/app/role/RoleManager.java +++ b/core/java/android/app/role/RoleManager.java @@ -22,6 +22,8 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; +import android.annotation.UserIdInt; import android.content.Context; import android.content.Intent; import android.os.Binder; @@ -29,8 +31,12 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.pooled.PooledLambda; import java.util.List; import java.util.concurrent.Executor; @@ -124,6 +130,13 @@ public final class RoleManager { @NonNull private final IRoleManager mService; + @GuardedBy("mListenersLock") + @NonNull + private final SparseArray<ArrayMap<OnRoleHoldersChangedListener, + OnRoleHoldersChangedListenerDelegate>> mListeners = new SparseArray<>(); + @NonNull + private final Object mListenersLock = new Object(); + /** * @hide */ @@ -145,8 +158,6 @@ public final class RoleManager { * @param roleName the name of requested role * * @return the {@code Intent} to prompt user to grant the role - * - * @throws IllegalArgumentException if {@code role} is {@code null} or empty */ @NonNull public Intent createRequestRoleIntent(@NonNull String roleName) { @@ -163,8 +174,6 @@ public final class RoleManager { * @param roleName the name of role to checking for * * @return whether the role is available in the system - * - * @throws IllegalArgumentException if the role name is {@code null} or empty */ public boolean isRoleAvailable(@NonNull String roleName) { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); @@ -181,8 +190,6 @@ public final class RoleManager { * @param roleName the name of the role to check for * * @return whether the calling application is holding the role - * - * @throws IllegalArgumentException if the role name is {@code null} or empty. */ public boolean isRoleHeld(@NonNull String roleName) { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); @@ -203,8 +210,6 @@ public final class RoleManager { * * @return a list of package names of the role holders, or an empty list if none. * - * @throws IllegalArgumentException if the role name is {@code null} or empty. - * * @see #getRoleHoldersAsUser(String, UserHandle) * * @hide @@ -212,6 +217,7 @@ public final class RoleManager { @NonNull @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) @SystemApi + @TestApi public List<String> getRoleHolders(@NonNull String roleName) { return getRoleHoldersAsUser(roleName, Process.myUserHandle()); } @@ -228,8 +234,6 @@ public final class RoleManager { * * @return a list of package names of the role holders, or an empty list if none. * - * @throws IllegalArgumentException if the role name is {@code null} or empty. - * * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback) @@ -239,6 +243,7 @@ public final class RoleManager { @NonNull @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) @SystemApi + @TestApi public List<String> getRoleHoldersAsUser(@NonNull String roleName, @NonNull UserHandle user) { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); Preconditions.checkNotNull(user, "user cannot be null"); @@ -263,8 +268,6 @@ public final class RoleManager { * @param executor the {@code Executor} to run the callback on. * @param callback the callback for whether this call is successful * - * @throws IllegalArgumentException if the role name or package name is {@code null} or empty. - * * @see #getRoleHoldersAsUser(String, UserHandle) * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback) @@ -273,6 +276,7 @@ public final class RoleManager { */ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) @SystemApi + @TestApi public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName, @NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor, @NonNull RoleManagerCallback callback) { @@ -302,8 +306,6 @@ public final class RoleManager { * @param executor the {@code Executor} to run the callback on. * @param callback the callback for whether this call is successful * - * @throws IllegalArgumentException if the role name or package name is {@code null} or empty. - * * @see #getRoleHoldersAsUser(String, UserHandle) * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback) @@ -312,6 +314,7 @@ public final class RoleManager { */ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) @SystemApi + @TestApi public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName, @NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor, @NonNull RoleManagerCallback callback) { @@ -340,8 +343,6 @@ public final class RoleManager { * @param executor the {@code Executor} to run the callback on. * @param callback the callback for whether this call is successful * - * @throws IllegalArgumentException if the role name is {@code null} or empty. - * * @see #getRoleHoldersAsUser(String, UserHandle) * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) @@ -350,6 +351,7 @@ public final class RoleManager { */ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) @SystemApi + @TestApi public void clearRoleHoldersAsUser(@NonNull String roleName, @NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor, @NonNull RoleManagerCallback callback) { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); @@ -365,6 +367,96 @@ public final class RoleManager { } /** + * Add a listener to observe role holder changes + * <p> + * <strong>Note:</strong> Using this API requires holding + * {@code android.permission.OBSERVE_ROLE_HOLDERS} and if the user id is not the current user + * {@code android.permission.INTERACT_ACROSS_USERS_FULL}. + * + * @param executor the {@code Executor} to call the listener on. + * @param listener the listener to be added + * @param user the user to add the listener for + * + * @see #removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener, UserHandle) + * + * @hide + */ + @RequiresPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS) + @SystemApi + public void addOnRoleHoldersChangedListenerAsUser(@CallbackExecutor @NonNull Executor executor, + @NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) { + Preconditions.checkNotNull(executor, "executor cannot be null"); + Preconditions.checkNotNull(listener, "listener cannot be null"); + Preconditions.checkNotNull(user, "user cannot be null"); + int userId = user.getIdentifier(); + synchronized (mListenersLock) { + ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners = + mListeners.get(userId); + if (listeners == null) { + listeners = new ArrayMap<>(); + mListeners.put(userId, listeners); + } else { + if (listeners.containsKey(listener)) { + return; + } + } + OnRoleHoldersChangedListenerDelegate listenerDelegate = + new OnRoleHoldersChangedListenerDelegate(executor, listener); + try { + mService.addOnRoleHoldersChangedListenerAsUser(listenerDelegate, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + listeners.put(listener, listenerDelegate); + } + } + + /** + * Remove a listener observing role holder changes + * <p> + * <strong>Note:</strong> Using this API requires holding + * {@code android.permission.OBSERVE_ROLE_HOLDERS} and if the user id is not the current user + * {@code android.permission.INTERACT_ACROSS_USERS_FULL}. + * + * @param listener the listener to be removed + * @param user the user to remove the listener for + * + * @see #addOnRoleHoldersChangedListenerAsUser(Executor, OnRoleHoldersChangedListener, + * UserHandle) + * + * @hide + */ + @RequiresPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS) + @SystemApi + public void removeOnRoleHoldersChangedListenerAsUser( + @NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) { + Preconditions.checkNotNull(listener, "listener cannot be null"); + Preconditions.checkNotNull(user, "user cannot be null"); + int userId = user.getIdentifier(); + synchronized (mListenersLock) { + ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners = + mListeners.get(userId); + if (listeners == null) { + return; + } + OnRoleHoldersChangedListenerDelegate listenerDelegate = listeners.get(listener); + if (listenerDelegate == null) { + return; + } + try { + mService.removeOnRoleHoldersChangedListenerAsUser(listenerDelegate, + user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + listeners.remove(listener); + if (listeners.isEmpty()) { + mListeners.remove(userId); + } + } + } + + /** * Set the names of all the available roles. Should only be called from * {@link android.rolecontrollerservice.RoleControllerService}. * <p> @@ -373,8 +465,6 @@ public final class RoleManager { * * @param roleNames the names of all the available roles * - * @throws IllegalArgumentException if the list of role names is {@code null}. - * * @hide */ @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER) @@ -402,8 +492,6 @@ public final class RoleManager { * @return whether the operation was successful, and will also be {@code true} if a matching * role holder is already found. * - * @throws IllegalArgumentException if the role name or package name is {@code null} or empty. - * * @see #getRoleHolders(String) * @see #removeRoleHolderFromController(String, String) * @@ -436,8 +524,6 @@ public final class RoleManager { * @return whether the operation was successful, and will also be {@code true} if no matching * role holder was found to remove. * - * @throws IllegalArgumentException if the role name or package name is {@code null} or empty. - * * @see #getRoleHolders(String) * @see #addRoleHolderFromController(String, String) * @@ -489,4 +575,31 @@ public final class RoleManager { } } } + + private static class OnRoleHoldersChangedListenerDelegate + extends IOnRoleHoldersChangedListener.Stub { + + @NonNull + private final Executor mExecutor; + @NonNull + private final OnRoleHoldersChangedListener mListener; + + OnRoleHoldersChangedListenerDelegate(@NonNull Executor executor, + @NonNull OnRoleHoldersChangedListener listener) { + mExecutor = executor; + mListener = listener; + } + + @Override + public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) { + long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(PooledLambda.obtainRunnable( + OnRoleHoldersChangedListener::onRoleHoldersChanged, mListener, roleName, + UserHandle.of(userId))); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } } diff --git a/core/java/android/app/role/RoleManagerCallback.java b/core/java/android/app/role/RoleManagerCallback.java index ca68ebc98303..d9f0a6c97f4d 100644 --- a/core/java/android/app/role/RoleManagerCallback.java +++ b/core/java/android/app/role/RoleManagerCallback.java @@ -17,6 +17,7 @@ package android.app.role; import android.annotation.SystemApi; +import android.annotation.TestApi; /** * Callback for a {@link RoleManager} request. @@ -24,6 +25,7 @@ import android.annotation.SystemApi; * @hide */ @SystemApi +@TestApi public interface RoleManagerCallback { /** diff --git a/core/java/android/app/usage/EventList.java b/core/java/android/app/usage/EventList.java index aaae57e526a0..a79ad2fc8607 100644 --- a/core/java/android/app/usage/EventList.java +++ b/core/java/android/app/usage/EventList.java @@ -103,4 +103,21 @@ public class EventList { } return result; } + + /** + * Remove events of certain type on or after a timestamp. + * @param type The type of event to remove. + * @param timeStamp the timeStamp on or after which to remove the event. + */ + public void removeOnOrAfter(int type, long timeStamp) { + for (int i = mEvents.size() - 1; i >= 0; i--) { + UsageEvents.Event event = mEvents.get(i); + if (event.mTimeStamp < timeStamp) { + break; + } + if (event.mEventType == type) { + mEvents.remove(i); + } + } + } } diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 3a5975aea628..a06213d77a68 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -50,13 +50,27 @@ public final class UsageEvents implements Parcelable { public static final int NONE = 0; /** + * @deprecated by {@link #ACTIVITY_RESUMED} + */ + @Deprecated + public static final int MOVE_TO_FOREGROUND = 1; + + /** * An event type denoting that an {@link android.app.Activity} moved to the foreground. * This event has a package name and class name associated with it and can be retrieved * using {@link #getPackageName()} and {@link #getClassName()}. * If a package has multiple activities, this event is reported for each activity that moves * to foreground. + * This event is corresponding to {@link android.app.Activity#onResume()} of the + * activity's lifecycle. */ - public static final int MOVE_TO_FOREGROUND = 1; + public static final int ACTIVITY_RESUMED = MOVE_TO_FOREGROUND; + + /** + * @deprecated by {@link #ACTIVITY_PAUSED} + */ + @Deprecated + public static final int MOVE_TO_BACKGROUND = 2; /** * An event type denoting that an {@link android.app.Activity} moved to the background. @@ -64,19 +78,21 @@ public final class UsageEvents implements Parcelable { * using {@link #getPackageName()} and {@link #getClassName()}. * If a package has multiple activities, this event is reported for each activity that moves * to background. + * This event is corresponding to {@link android.app.Activity#onPause()} of the activity's + * lifecycle. */ - public static final int MOVE_TO_BACKGROUND = 2; + public static final int ACTIVITY_PAUSED = MOVE_TO_BACKGROUND; /** * An event type denoting that a component was in the foreground when the stats - * rolled-over. This is effectively treated as a {@link #MOVE_TO_BACKGROUND}. + * rolled-over. This is effectively treated as a {@link #ACTIVITY_PAUSED}. * {@hide} */ public static final int END_OF_DAY = 3; /** * An event type denoting that a component was in the foreground the previous day. - * This is effectively treated as a {@link #MOVE_TO_FOREGROUND}. + * This is effectively treated as a {@link #ACTIVITY_RESUMED}. * {@hide} */ public static final int CONTINUE_PREVIOUS_DAY = 4; @@ -207,10 +223,31 @@ public final class UsageEvents implements Parcelable { public static final int ROLLOVER_FOREGROUND_SERVICE = 22; /** + * An activity becomes invisible on the UI, corresponding to + * {@link android.app.Activity#onStop()} of the activity's lifecycle. + */ + public static final int ACTIVITY_STOPPED = 23; + + /** + * An activity object is destroyed, corresponding to + * {@link android.app.Activity#onDestroy()} of the activity's lifecycle. + * {@hide} + */ + public static final int ACTIVITY_DESTROYED = 24; + + /** + * The event type demoting that a flush of UsageStatsDatabase to file system. Before the + * flush all usage stats need to be updated to latest timestamp to make sure the most + * up to date stats are persisted. + * @hide + */ + public static final int FLUSH_TO_DISK = 25; + + /** * Keep in sync with the greatest event type value. * @hide */ - public static final int MAX_EVENT_TYPE = 22; + public static final int MAX_EVENT_TYPE = 25; /** @hide */ public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; @@ -240,6 +277,12 @@ public final class UsageEvents implements Parcelable { @UnsupportedAppUsage public String mClass; + + /** + * {@hide} + */ + public int mInstanceId; + /** * {@hide} */ @@ -311,9 +354,16 @@ public final class UsageEvents implements Parcelable { } /** @hide */ + public Event(int type, long timeStamp) { + mEventType = type; + mTimeStamp = timeStamp; + } + + /** @hide */ public Event(Event orig) { mPackage = orig.mPackage; mClass = orig.mClass; + mInstanceId = orig.mInstanceId; mTimeStamp = orig.mTimeStamp; mEventType = orig.mEventType; mConfiguration = orig.mConfiguration; @@ -342,6 +392,16 @@ public final class UsageEvents implements Parcelable { } /** + * An activity can be instantiated multiple times, this is the unique activity instance ID. + * For non-activity class, instance ID is always zero. + * @hide + */ + @SystemApi + public int getInstanceId() { + return mInstanceId; + } + + /** * The time at which this event occurred, measured in milliseconds since the epoch. * <p/> * See {@link System#currentTimeMillis()}. @@ -352,12 +412,14 @@ public final class UsageEvents implements Parcelable { /** * The event type. - * - * @see #MOVE_TO_BACKGROUND - * @see #MOVE_TO_FOREGROUND + * @see #ACTIVITY_PAUSED + * @see #ACTIVITY_RESUMED * @see #CONFIGURATION_CHANGE * @see #USER_INTERACTION * @see #STANDBY_BUCKET_CHANGED + * @see #FOREGROUND_SERVICE_START + * @see #FOREGROUND_SERVICE_STOP + * @see #ACTIVITY_STOPPED */ public int getEventType() { return mEventType; @@ -576,6 +638,7 @@ public final class UsageEvents implements Parcelable { } p.writeInt(packageIndex); p.writeInt(classIndex); + p.writeInt(event.mInstanceId); p.writeInt(event.mEventType); p.writeLong(event.mTimeStamp); @@ -618,6 +681,7 @@ public final class UsageEvents implements Parcelable { } else { eventOut.mClass = null; } + eventOut.mInstanceId = p.readInt(); eventOut.mEventType = p.readInt(); eventOut.mTimeStamp = p.readLong(); diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java index 73426e495037..8fb7f4cb4d99 100644 --- a/core/java/android/app/usage/UsageStats.java +++ b/core/java/android/app/usage/UsageStats.java @@ -16,13 +16,15 @@ package android.app.usage; -import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY; +import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED; import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE; import static android.app.usage.UsageEvents.Event.END_OF_DAY; +import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK; import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START; import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP; -import static android.app.usage.UsageEvents.Event.MOVE_TO_BACKGROUND; -import static android.app.usage.UsageEvents.Event.MOVE_TO_FOREGROUND; import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE; import android.annotation.SystemApi; @@ -31,6 +33,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; +import android.util.SparseIntArray; /** * Contains usage statistics for an app package for a specific @@ -57,13 +60,20 @@ public final class UsageStats implements Parcelable { public long mEndTimeStamp; /** - * Last time used by the user with an explicit action (notification, activity launch) + * Last time an activity is at foreground (have focus), this is corresponding to + * {@link android.app.usage.UsageEvents.Event#ACTIVITY_RESUMED} event. * {@hide} */ @UnsupportedAppUsage public long mLastTimeUsed; /** + * Last time an activity is visible. + * @hide + */ + public long mLastTimeVisible; + + /** * Total time this package's activity is in foreground. * {@hide} */ @@ -71,6 +81,12 @@ public final class UsageStats implements Parcelable { public long mTotalTimeInForeground; /** + * Total time this package's activity is visible. + * {@hide} + */ + public long mTotalTimeVisible; + + /** * Last time foreground service is started. * {@hide} */ @@ -93,31 +109,32 @@ public final class UsageStats implements Parcelable { */ public int mAppLaunchCount; - /** Last activity MOVE_TO_FOREGROUND or MOVE_TO_BACKGROUND event. + /** Last activity ACTIVITY_RESUMED or ACTIVITY_PAUSED event. * {@hide} - * @deprecated use {@link #mLastForegroundActivityEventMap} instead. + * @deprecated use {@link #mActivities} instead. */ @UnsupportedAppUsage @Deprecated public int mLastEvent; /** - * If an activity is in foreground, it has one entry in this map. - * When activity moves to background, it is removed from this map. - * Key is activity class name. - * Value is last time this activity MOVE_TO_FOREGROUND or MOVE_TO_BACKGROUND event. + * If an activity is visible(onStart(), onPause() states) or in foreground (onResume() state), + * it has one entry in this map. When an activity becomes invisible (onStop() or onDestroy()), + * it is removed from this map. + * Key is instanceId of the activity (ActivityRecode appToken hashCode).. + * Value is this activity's last event, one of ACTIVITY_RESUMED or + * ACTIVITY_PAUSED. * {@hide} */ - public ArrayMap<String, Integer> mLastForegroundActivityEventMap = new ArrayMap<>(); - + public SparseIntArray mActivities = new SparseIntArray(); /** * If a foreground service is started, it has one entry in this map. - * When a foreground service is stopped, it is removed from this map. + * When a foreground service is stopped, it is removed from this set. * Key is foreground service class name. - * Value is last foreground service FOREGROUND_SERVICE_START ot FOREGROUND_SERVICE_STOP event. + * Value is the foreground service's last event, it is FOREGROUND_SERVICE_START. * {@hide} */ - public ArrayMap<String, Integer> mLastForegroundServiceEventMap = new ArrayMap<>(); + public ArrayMap<String, Integer> mForegroundServices = new ArrayMap<>(); /** * {@hide} @@ -135,14 +152,16 @@ public final class UsageStats implements Parcelable { mBeginTimeStamp = stats.mBeginTimeStamp; mEndTimeStamp = stats.mEndTimeStamp; mLastTimeUsed = stats.mLastTimeUsed; + mLastTimeVisible = stats.mLastTimeVisible; mLastTimeForegroundServiceUsed = stats.mLastTimeForegroundServiceUsed; mTotalTimeInForeground = stats.mTotalTimeInForeground; + mTotalTimeVisible = stats.mTotalTimeVisible; mTotalTimeForegroundServiceUsed = stats.mTotalTimeForegroundServiceUsed; mLaunchCount = stats.mLaunchCount; mAppLaunchCount = stats.mAppLaunchCount; mLastEvent = stats.mLastEvent; - mLastForegroundActivityEventMap = stats.mLastForegroundActivityEventMap; - mLastForegroundServiceEventMap = stats.mLastForegroundServiceEventMap; + mActivities = stats.mActivities; + mForegroundServices = stats.mForegroundServices; mChooserCounts = stats.mChooserCounts; } @@ -191,6 +210,14 @@ public final class UsageStats implements Parcelable { } /** + * Get the last time this package's activity is visible in the UI, measured in milliseconds + * since the epoch. + */ + public long getLastTimeVisible() { + return mLastTimeVisible; + } + + /** * Get the total time this package spent in the foreground, measured in milliseconds. */ public long getTotalTimeInForeground() { @@ -198,6 +225,13 @@ public final class UsageStats implements Parcelable { } /** + * Get the total time this package's activity is visible in the UI, measured in milliseconds. + */ + public long getTotalTimeVisible() { + return mTotalTimeVisible; + } + + /** * Get the last time this package's foreground service was used, measured in milliseconds since * the epoch. * <p/> @@ -224,6 +258,20 @@ public final class UsageStats implements Parcelable { return mAppLaunchCount; } + private void mergeEventMap(SparseIntArray left, SparseIntArray right) { + final int size = right.size(); + for (int i = 0; i < size; i++) { + final int instanceId = right.keyAt(i); + final int event = right.valueAt(i); + final int index = left.indexOfKey(instanceId); + if (index >= 0) { + left.put(instanceId, Math.max(left.valueAt(index), event)); + } else { + left.put(instanceId, event); + } + } + } + private void mergeEventMap(ArrayMap<String, Integer> left, ArrayMap<String, Integer> right) { final int size = right.size(); for (int i = 0; i < size; i++) { @@ -255,15 +303,17 @@ public final class UsageStats implements Parcelable { if (right.mBeginTimeStamp > mBeginTimeStamp) { // Even though incoming UsageStat begins after this one, its last time used fields // may somehow be empty or chronologically preceding the older UsageStat. - mergeEventMap(mLastForegroundActivityEventMap, right.mLastForegroundActivityEventMap); - mergeEventMap(mLastForegroundServiceEventMap, right.mLastForegroundServiceEventMap); + mergeEventMap(mActivities, right.mActivities); + mergeEventMap(mForegroundServices, right.mForegroundServices); mLastTimeUsed = Math.max(mLastTimeUsed, right.mLastTimeUsed); + mLastTimeVisible = Math.max(mLastTimeVisible, right.mLastTimeVisible); mLastTimeForegroundServiceUsed = Math.max(mLastTimeForegroundServiceUsed, right.mLastTimeForegroundServiceUsed); } mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp); mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp); mTotalTimeInForeground += right.mTotalTimeInForeground; + mTotalTimeVisible += right.mTotalTimeVisible; mTotalTimeForegroundServiceUsed += right.mTotalTimeForegroundServiceUsed; mLaunchCount += right.mLaunchCount; mAppLaunchCount += right.mAppLaunchCount; @@ -290,36 +340,76 @@ public final class UsageStats implements Parcelable { } /** - * Tell if an event indicate activity is in foreground or not. - * @param event the activity event. - * @return true if activity is in foreground, false otherwise. - * @hide + * Tell if any activity is in foreground. + * @return */ - private boolean isActivityInForeground(int event) { - return event == MOVE_TO_FOREGROUND - || event == CONTINUE_PREVIOUS_DAY; + private boolean hasForegroundActivity() { + final int size = mActivities.size(); + for (int i = 0; i < size; i++) { + if (mActivities.valueAt(i) == ACTIVITY_RESUMED) { + return true; + } + } + return false; } /** - * Tell if an event indicate foreground sevice is started or not. - * @param event the foreground service event. - * @return true if foreground service is started, false if stopped. - * @hide + * Tell if any activity is visible. + * @return */ - private boolean isForegroundServiceStarted(int event) { - return event == FOREGROUND_SERVICE_START - || event == CONTINUING_FOREGROUND_SERVICE; + private boolean hasVisibleActivity() { + final int size = mActivities.size(); + for (int i = 0; i < size; i++) { + final int type = mActivities.valueAt(i); + if (type == ACTIVITY_RESUMED + || type == ACTIVITY_PAUSED) { + return true; + } + } + return false; } /** - * If any activity in foreground or any foreground service is started, the app is considered in - * use. - * @return true if in use, false otherwise. - * @hide + * Tell if any foreground service is started. + * @return + */ + private boolean anyForegroundServiceStarted() { + return !mForegroundServices.isEmpty(); + } + + /** + * Increment total time in foreground and update last time in foreground. + * @param timeStamp current timestamp. + */ + private void incrementTimeUsed(long timeStamp) { + if (timeStamp > mLastTimeUsed) { + mTotalTimeInForeground += timeStamp - mLastTimeUsed; + mLastTimeUsed = timeStamp; + } + } + + /** + * Increment total time visible and update last time visible. + * @param timeStamp current timestmap. */ - private boolean isAppInUse() { - return !mLastForegroundActivityEventMap.isEmpty() - || !mLastForegroundServiceEventMap.isEmpty(); + private void incrementTimeVisible(long timeStamp) { + if (timeStamp > mLastTimeVisible) { + mTotalTimeVisible += timeStamp - mLastTimeVisible; + mLastTimeVisible = timeStamp; + } + } + + /** + * Increment total time foreground service is used and update last time foreground service is + * used. + * @param timeStamp current timestamp. + */ + private void incrementServiceTimeUsed(long timeStamp) { + if (timeStamp > mLastTimeForegroundServiceUsed) { + mTotalTimeForegroundServiceUsed += + timeStamp - mLastTimeForegroundServiceUsed; + mLastTimeForegroundServiceUsed = timeStamp; + } } /** @@ -327,33 +417,63 @@ public final class UsageStats implements Parcelable { * @param className className of the activity. * @param timeStamp timeStamp of the event. * @param eventType type of the event. + * @param instanceId hashCode of the ActivityRecord's appToken. * @hide */ - private void updateForegroundActivity(String className, long timeStamp, int eventType) { - if (eventType != MOVE_TO_BACKGROUND - && eventType != MOVE_TO_FOREGROUND - && eventType != END_OF_DAY) { + private void updateActivity(String className, long timeStamp, int eventType, int instanceId) { + if (eventType != ACTIVITY_RESUMED + && eventType != ACTIVITY_PAUSED + && eventType != ACTIVITY_STOPPED + && eventType != ACTIVITY_DESTROYED) { return; } - final Integer lastEvent = mLastForegroundActivityEventMap.get(className); - if (lastEvent != null) { - if (isActivityInForeground(lastEvent)) { - if (timeStamp > mLastTimeUsed) { - mTotalTimeInForeground += timeStamp - mLastTimeUsed; + // update usage. + final int index = mActivities.indexOfKey(instanceId); + if (index >= 0) { + final int lastEvent = mActivities.valueAt(index); + switch (lastEvent) { + case ACTIVITY_RESUMED: + incrementTimeUsed(timeStamp); + incrementTimeVisible(timeStamp); + break; + case ACTIVITY_PAUSED: + incrementTimeVisible(timeStamp); + break; + default: + break; + } + } + + // update current event. + switch(eventType) { + case ACTIVITY_RESUMED: + if (!hasVisibleActivity()) { + // this is the first visible activity. + mLastTimeUsed = timeStamp; + mLastTimeVisible = timeStamp; + } else if (!hasForegroundActivity()) { + // this is the first foreground activity. mLastTimeUsed = timeStamp; } - } - if (eventType == MOVE_TO_BACKGROUND) { - mLastForegroundActivityEventMap.remove(className); - } else { - mLastForegroundActivityEventMap.put(className, eventType); - } - } else if (eventType == MOVE_TO_FOREGROUND) { - if (!isAppInUse()) { - mLastTimeUsed = timeStamp; - } - mLastForegroundActivityEventMap.put(className, eventType); + mActivities.put(instanceId, eventType); + break; + case ACTIVITY_PAUSED: + if (!hasVisibleActivity()) { + // this is the first visible activity. + mLastTimeVisible = timeStamp; + } + mActivities.put(instanceId, eventType); + break; + case ACTIVITY_STOPPED: + mActivities.put(instanceId, eventType); + break; + case ACTIVITY_DESTROYED: + // remove activity from the map. + mActivities.delete(instanceId); + break; + default: + break; } } @@ -366,80 +486,97 @@ public final class UsageStats implements Parcelable { */ private void updateForegroundService(String className, long timeStamp, int eventType) { if (eventType != FOREGROUND_SERVICE_STOP - && eventType != FOREGROUND_SERVICE_START - && eventType != ROLLOVER_FOREGROUND_SERVICE) { + && eventType != FOREGROUND_SERVICE_START) { return; } - final Integer lastEvent = mLastForegroundServiceEventMap.get(className); + final Integer lastEvent = mForegroundServices.get(className); + // update usage. if (lastEvent != null) { - if (isForegroundServiceStarted(lastEvent)) { - if (timeStamp > mLastTimeForegroundServiceUsed) { - mTotalTimeForegroundServiceUsed += - timeStamp - mLastTimeForegroundServiceUsed; + switch (lastEvent) { + case FOREGROUND_SERVICE_START: + case CONTINUING_FOREGROUND_SERVICE: + incrementServiceTimeUsed(timeStamp); + break; + default: + break; + } + } + + // update current event. + switch (eventType) { + case FOREGROUND_SERVICE_START: + if (!anyForegroundServiceStarted()) { mLastTimeForegroundServiceUsed = timeStamp; } - } - if (eventType == FOREGROUND_SERVICE_STOP) { - mLastForegroundServiceEventMap.remove(className); - } else { - mLastForegroundServiceEventMap.put(className, eventType); - } - } else if (eventType == FOREGROUND_SERVICE_START) { - if (!isAppInUse()) { - mLastTimeForegroundServiceUsed = timeStamp; - } - mLastForegroundServiceEventMap.put(className, eventType); + mForegroundServices.put(className, eventType); + break; + case FOREGROUND_SERVICE_STOP: + mForegroundServices.remove(className); + break; + default: + break; } } /** * Update the UsageStats by a activity or foreground service event. - * @param className class name of a activity or foreground service, could be null to mark - * END_OF_DAY or rollover. + * @param className class name of a activity or foreground service, could be null to if this + * is sent to all activities/services in this package. * @param timeStamp Epoch timestamp in milliseconds. * @param eventType event type as in {@link UsageEvents.Event} + * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken. + * if className is not an activity, instanceId is not used. * @hide */ - public void update(String className, long timeStamp, int eventType) { + public void update(String className, long timeStamp, int eventType, int instanceId) { switch(eventType) { - case MOVE_TO_BACKGROUND: - case MOVE_TO_FOREGROUND: - updateForegroundActivity(className, timeStamp, eventType); + case ACTIVITY_RESUMED: + case ACTIVITY_PAUSED: + case ACTIVITY_STOPPED: + case ACTIVITY_DESTROYED: + updateActivity(className, timeStamp, eventType, instanceId); break; case END_OF_DAY: - // END_OF_DAY means updating all activities. - final int size = mLastForegroundActivityEventMap.size(); - for (int i = 0; i < size; i++) { - final String name = mLastForegroundActivityEventMap.keyAt(i); - updateForegroundActivity(name, timeStamp, eventType); + // END_OF_DAY updates all activities. + if (hasForegroundActivity()) { + incrementTimeUsed(timeStamp); + } + if (hasVisibleActivity()) { + incrementTimeVisible(timeStamp); } break; - case CONTINUE_PREVIOUS_DAY: - mLastTimeUsed = timeStamp; - mLastForegroundActivityEventMap.put(className, eventType); - break; - case FOREGROUND_SERVICE_STOP: case FOREGROUND_SERVICE_START: + case FOREGROUND_SERVICE_STOP: updateForegroundService(className, timeStamp, eventType); break; case ROLLOVER_FOREGROUND_SERVICE: - // ROLLOVER_FOREGROUND_SERVICE means updating all foreground services. - final int size2 = mLastForegroundServiceEventMap.size(); - for (int i = 0; i < size2; i++) { - final String name = mLastForegroundServiceEventMap.keyAt(i); - updateForegroundService(name, timeStamp, eventType); + // ROLLOVER_FOREGROUND_SERVICE updates all foreground services. + if (anyForegroundServiceStarted()) { + incrementServiceTimeUsed(timeStamp); } break; case CONTINUING_FOREGROUND_SERVICE: mLastTimeForegroundServiceUsed = timeStamp; - mLastForegroundServiceEventMap.put(className, eventType); + mForegroundServices.put(className, eventType); + break; + case FLUSH_TO_DISK: + // update usage of all active activities/services. + if (hasForegroundActivity()) { + incrementTimeUsed(timeStamp); + } + if (hasVisibleActivity()) { + incrementTimeVisible(timeStamp); + } + if (anyForegroundServiceStarted()) { + incrementServiceTimeUsed(timeStamp); + } break; default: break; } mEndTimeStamp = timeStamp; - if (eventType == MOVE_TO_FOREGROUND) { + if (eventType == ACTIVITY_RESUMED) { mLaunchCount += 1; } } @@ -455,8 +592,10 @@ public final class UsageStats implements Parcelable { dest.writeLong(mBeginTimeStamp); dest.writeLong(mEndTimeStamp); dest.writeLong(mLastTimeUsed); + dest.writeLong(mLastTimeVisible); dest.writeLong(mLastTimeForegroundServiceUsed); dest.writeLong(mTotalTimeInForeground); + dest.writeLong(mTotalTimeVisible); dest.writeLong(mTotalTimeForegroundServiceUsed); dest.writeInt(mLaunchCount); dest.writeInt(mAppLaunchCount); @@ -477,21 +616,26 @@ public final class UsageStats implements Parcelable { } dest.writeBundle(allCounts); - final Bundle foregroundActivityEventBundle = new Bundle(); - final int foregroundEventSize = mLastForegroundActivityEventMap.size(); - for (int i = 0; i < foregroundEventSize; i++) { - foregroundActivityEventBundle.putInt(mLastForegroundActivityEventMap.keyAt(i), - mLastForegroundActivityEventMap.valueAt(i)); + writeSparseIntArray(dest, mActivities); + dest.writeBundle(eventMapToBundle(mForegroundServices)); + } + + private void writeSparseIntArray(Parcel dest, SparseIntArray arr) { + final int size = arr.size(); + dest.writeInt(size); + for (int i = 0; i < size; i++) { + dest.writeInt(arr.keyAt(i)); + dest.writeInt(arr.valueAt(i)); } - dest.writeBundle(foregroundActivityEventBundle); + } - final Bundle foregroundServiceEventBundle = new Bundle(); - final int foregroundServiceEventSize = mLastForegroundServiceEventMap.size(); - for (int i = 0; i < foregroundServiceEventSize; i++) { - foregroundServiceEventBundle.putInt(mLastForegroundServiceEventMap.keyAt(i), - mLastForegroundServiceEventMap.valueAt(i)); + private Bundle eventMapToBundle(ArrayMap<String, Integer> eventMap) { + final Bundle bundle = new Bundle(); + final int size = eventMap.size(); + for (int i = 0; i < size; i++) { + bundle.putInt(eventMap.keyAt(i), eventMap.valueAt(i)); } - dest.writeBundle(foregroundServiceEventBundle); + return bundle; } public static final Creator<UsageStats> CREATOR = new Creator<UsageStats>() { @@ -502,8 +646,10 @@ public final class UsageStats implements Parcelable { stats.mBeginTimeStamp = in.readLong(); stats.mEndTimeStamp = in.readLong(); stats.mLastTimeUsed = in.readLong(); + stats.mLastTimeVisible = in.readLong(); stats.mLastTimeForegroundServiceUsed = in.readLong(); stats.mTotalTimeInForeground = in.readLong(); + stats.mTotalTimeVisible = in.readLong(); stats.mTotalTimeForegroundServiceUsed = in.readLong(); stats.mLaunchCount = in.readInt(); stats.mAppLaunchCount = in.readInt(); @@ -527,12 +673,21 @@ public final class UsageStats implements Parcelable { } } } - readBundleToEventMap(stats.mLastForegroundActivityEventMap, in.readBundle()); - readBundleToEventMap(stats.mLastForegroundServiceEventMap, in.readBundle()); + readSparseIntArray(in, stats.mActivities); + readBundleToEventMap(in.readBundle(), stats.mForegroundServices); return stats; } - private void readBundleToEventMap(ArrayMap<String, Integer> eventMap, Bundle bundle) { + private void readSparseIntArray(Parcel in, SparseIntArray arr) { + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + final int key = in.readInt(); + final int value = in.readInt(); + arr.put(key, value); + } + } + + private void readBundleToEventMap(Bundle bundle, ArrayMap<String, Integer> eventMap) { if (bundle != null) { for (String className : bundle.keySet()) { final int event = bundle.getInt(className); diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index 1a656ab39373..2edad350e18e 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -37,9 +37,12 @@ public abstract class UsageStatsManagerInternal { * @param component The component for which this event occurred. * @param userId The user id to which the component belongs to. * @param eventType The event that occurred. Valid values can be found at - * {@link UsageEvents} + * {@link UsageEvents} + * @param instanceId For activity, hashCode of ActivityRecord's appToken. + * For non-activity, it is not used. */ - public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType); + public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType, + int instanceId); /** * Reports an event to the UsageStatsManager. diff --git a/core/java/android/content/ContentInterface.java b/core/java/android/content/ContentInterface.java new file mode 100644 index 000000000000..3d732eb7678d --- /dev/null +++ b/core/java/android/content/ContentInterface.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018 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.content; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import java.io.FileNotFoundException; +import java.util.ArrayList; + +/** + * Interface representing calls that can be made to {@link ContentProvider} + * instances. + * <p> + * These methods have been extracted into a general interface so that APIs can + * be flexible in accepting either a {@link ContentProvider}, a + * {@link ContentResolver}, or a {@link ContentProviderClient}. + */ +public interface ContentInterface { + public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection, + @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) + throws RemoteException; + + public @Nullable String getType(@NonNull Uri uri) throws RemoteException; + + public @Nullable String[] getStreamTypes(@NonNull Uri uri, @NonNull String mimeTypeFilter) + throws RemoteException; + + public @Nullable Uri canonicalize(@NonNull Uri uri) throws RemoteException; + + public @Nullable Uri uncanonicalize(@NonNull Uri uri) throws RemoteException; + + public boolean refresh(@NonNull Uri uri, @Nullable Bundle args, + @Nullable CancellationSignal cancellationSignal) throws RemoteException; + + public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues initialValues) + throws RemoteException; + + public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] initialValues) + throws RemoteException; + + public int delete(@NonNull Uri uri, @Nullable String selection, + @Nullable String[] selectionArgs) throws RemoteException; + + public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, + @Nullable String[] selectionArgs) throws RemoteException; + + public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode, + @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException; + + public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode, + @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException; + + public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri, + @NonNull String mimeTypeFilter, @Nullable Bundle opts, + @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException; + + public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority, + @NonNull ArrayList<ContentProviderOperation> operations) + throws RemoteException, OperationApplicationException; + + public @Nullable Bundle call(@NonNull String authority, @NonNull String method, + @Nullable String arg, @Nullable Bundle extras) throws RemoteException; +} diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 0f72fd3c7631..5a12e4ecca1b 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -105,7 +105,7 @@ import java.util.Objects; * developer guide.</p> * </div> */ -public abstract class ContentProvider implements ComponentCallbacks2 { +public abstract class ContentProvider implements ContentInterface, ComponentCallbacks2 { private static final String TAG = "ContentProvider"; @@ -324,7 +324,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } @Override - public ContentProviderResult[] applyBatch(String callingPkg, + public ContentProviderResult[] applyBatch(String callingPkg, String authority, ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { int numOperations = operations.size(); @@ -356,7 +356,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { Trace.traceBegin(TRACE_TAG_DATABASE, "applyBatch"); final String original = setCallingPackage(callingPkg); try { - ContentProviderResult[] results = ContentProvider.this.applyBatch(operations); + ContentProviderResult[] results = ContentProvider.this.applyBatch(authority, + operations); if (results != null) { for (int i = 0; i < results.length ; i++) { if (userIds[i] != UserHandle.USER_CURRENT) { @@ -444,13 +445,13 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } @Override - public Bundle call( - String callingPkg, String method, @Nullable String arg, @Nullable Bundle extras) { + public Bundle call(String callingPkg, String authority, String method, @Nullable String arg, + @Nullable Bundle extras) { Bundle.setDefusable(extras, true); Trace.traceBegin(TRACE_TAG_DATABASE, "call"); final String original = setCallingPackage(callingPkg); try { - return ContentProvider.this.call(method, arg, extras); + return ContentProvider.this.call(authority, method, arg, extras); } finally { setCallingPackage(original); Trace.traceEnd(TRACE_TAG_DATABASE); @@ -1255,6 +1256,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * or {@code null}. * @return a Cursor or {@code null}. */ + @Override public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) { queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY; @@ -1293,6 +1295,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * @param uri the URI to query. * @return a MIME type string, or {@code null} if there is no type. */ + @Override public abstract @Nullable String getType(@NonNull Uri uri); /** @@ -1325,6 +1328,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * @return Return the canonical representation of <var>url</var>, or null if * canonicalization of that Uri is not supported. */ + @Override public @Nullable Uri canonicalize(@NonNull Uri url) { return null; } @@ -1343,6 +1347,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * the data identified by the canonical representation can not be found in * the current environment. */ + @Override public @Nullable Uri uncanonicalize(@NonNull Uri url) { return url; } @@ -1369,6 +1374,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * canceled the refresh request. * @return true if the provider actually tried refreshing. */ + @Override public boolean refresh(Uri uri, @Nullable Bundle args, @Nullable CancellationSignal cancellationSignal) { return false; @@ -1403,6 +1409,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * This must not be {@code null}. * @return The URI for the newly inserted item. */ + @Override public abstract @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values); /** @@ -1420,6 +1427,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * This must not be {@code null}. * @return The number of values that were inserted. */ + @Override public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) { int numValues = values.length; for (int i = 0; i < numValues; i++) { @@ -1448,6 +1456,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * @return The number of rows affected. * @throws SQLException */ + @Override public abstract int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs); @@ -1468,6 +1477,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * @param selection An optional filter to match rows to update. * @return the number of rows affected. */ + @Override public abstract int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs); @@ -1604,6 +1614,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * @see #getType(android.net.Uri) * @see ParcelFileDescriptor#parseMode(String) */ + @Override public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode, @Nullable CancellationSignal signal) throws FileNotFoundException { return openFile(uri, mode); @@ -1723,6 +1734,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * @see #openFileHelper(Uri, String) * @see #getType(android.net.Uri) */ + @Override public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode, @Nullable CancellationSignal signal) throws FileNotFoundException { return openAssetFile(uri, mode); @@ -1789,6 +1801,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * @see #openTypedAssetFile(Uri, String, Bundle) * @see ClipDescription#compareMimeTypes(String, String) */ + @Override public @Nullable String[] getStreamTypes(@NonNull Uri uri, @NonNull String mimeTypeFilter) { return null; } @@ -1905,6 +1918,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * @see #openAssetFile(Uri, String) * @see ClipDescription#compareMimeTypes(String, String) */ + @Override public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri, @NonNull String mimeTypeFilter, @Nullable Bundle opts, @Nullable CancellationSignal signal) throws FileNotFoundException { @@ -2059,6 +2073,13 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * @throws OperationApplicationException thrown if any operation fails. * @see ContentProviderOperation#apply */ + @Override + public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority, + @NonNull ArrayList<ContentProviderOperation> operations) + throws OperationApplicationException { + return applyBatch(operations); + } + public @NonNull ContentProviderResult[] applyBatch( @NonNull ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { @@ -2088,6 +2109,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * @return provider-defined return value. May be {@code null}, which is also * the default for providers which don't implement any call methods. */ + @Override + public @Nullable Bundle call(@NonNull String authority, @NonNull String method, + @Nullable String arg, @Nullable Bundle extras) { + return call(method, arg, extras); + } + public @Nullable Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) { return null; @@ -2133,6 +2160,19 @@ public abstract class ContentProvider implements ComponentCallbacks2 { writer.println("nothing to dump"); } + private void validateIncomingAuthority(String authority) throws SecurityException { + if (!matchesOurAuthorities(getAuthorityWithoutUserId(authority))) { + String message = "The authority " + authority + " does not match the one of the " + + "contentProvider: "; + if (mAuthority != null) { + message += mAuthority; + } else { + message += Arrays.toString(mAuthorities); + } + throw new SecurityException(message); + } + } + /** @hide */ @VisibleForTesting public Uri validateIncomingUri(Uri uri) throws SecurityException { @@ -2144,16 +2184,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { + mContext.getUserId() + " with a uri belonging to user " + userId); } } - if (!matchesOurAuthorities(getAuthorityWithoutUserId(auth))) { - String message = "The authority of the uri " + uri + " does not match the one of the " - + "contentProvider: "; - if (mAuthority != null) { - message += mAuthority; - } else { - message += Arrays.toString(mAuthorities); - } - throw new SecurityException(message); - } + validateIncomingAuthority(auth); // Normalize the path by removing any empty path segments, which can be // a source of security issues. diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index d315f4945ffe..0b5bdb5bfd4f 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -16,8 +16,12 @@ package android.content; +import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.content.res.AssetFileDescriptor; import android.database.CrossProcessCursorWrapper; @@ -65,7 +69,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * on the ContentProviderClient those calls are made from until you are finished * with the data they have returned. */ -public class ContentProviderClient implements AutoCloseable { +public class ContentProviderClient implements ContentInterface, AutoCloseable { private static final String TAG = "ContentProviderClient"; @GuardedBy("ContentProviderClient.class") @@ -76,6 +80,7 @@ public class ContentProviderClient implements AutoCloseable { private final IContentProvider mContentProvider; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final String mPackageName; + private final String mAuthority; private final boolean mStable; private final AtomicBoolean mClosed = new AtomicBoolean(); @@ -86,19 +91,39 @@ public class ContentProviderClient implements AutoCloseable { /** {@hide} */ @VisibleForTesting - public ContentProviderClient( - ContentResolver contentResolver, IContentProvider contentProvider, boolean stable) { + public ContentProviderClient(ContentResolver contentResolver, IContentProvider contentProvider, + boolean stable) { + // Only used for testing, so use a fake authority + this(contentResolver, contentProvider, "unknown", stable); + } + + /** {@hide} */ + public ContentProviderClient(ContentResolver contentResolver, IContentProvider contentProvider, + String authority, boolean stable) { mContentResolver = contentResolver; mContentProvider = contentProvider; mPackageName = contentResolver.mPackageName; + mAuthority = authority; mStable = stable; mCloseGuard.open("close"); } - /** {@hide} */ - public void setDetectNotResponding(long timeoutMillis) { + /** + * Configure this client to automatically detect and kill the remote + * provider when an "application not responding" event is detected. + * + * @param timeoutMillis the duration for which a pending call is allowed + * block before the remote provider is considered to be + * unresponsive. Set to {@code 0} to allow pending calls to block + * indefinitely with no action taken. + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.REMOVE_TASKS) + public void setDetectNotResponding(@DurationMillisLong long timeoutMillis) { synchronized (ContentProviderClient.class) { mAnrTimeout = timeoutMillis; @@ -153,6 +178,7 @@ public class ContentProviderClient implements AutoCloseable { } /** See {@link ContentProvider#query ContentProvider.query} */ + @Override public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection, Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) throws RemoteException { @@ -183,6 +209,7 @@ public class ContentProviderClient implements AutoCloseable { } /** See {@link ContentProvider#getType ContentProvider.getType} */ + @Override public @Nullable String getType(@NonNull Uri url) throws RemoteException { Preconditions.checkNotNull(url, "url"); @@ -200,6 +227,7 @@ public class ContentProviderClient implements AutoCloseable { } /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */ + @Override public @Nullable String[] getStreamTypes(@NonNull Uri url, @NonNull String mimeTypeFilter) throws RemoteException { Preconditions.checkNotNull(url, "url"); @@ -219,6 +247,7 @@ public class ContentProviderClient implements AutoCloseable { } /** See {@link ContentProvider#canonicalize} */ + @Override public final @Nullable Uri canonicalize(@NonNull Uri url) throws RemoteException { Preconditions.checkNotNull(url, "url"); @@ -236,6 +265,7 @@ public class ContentProviderClient implements AutoCloseable { } /** See {@link ContentProvider#uncanonicalize} */ + @Override public final @Nullable Uri uncanonicalize(@NonNull Uri url) throws RemoteException { Preconditions.checkNotNull(url, "url"); @@ -253,6 +283,7 @@ public class ContentProviderClient implements AutoCloseable { } /** See {@link ContentProvider#refresh} */ + @Override public boolean refresh(Uri url, @Nullable Bundle args, @Nullable CancellationSignal cancellationSignal) throws RemoteException { Preconditions.checkNotNull(url, "url"); @@ -277,6 +308,7 @@ public class ContentProviderClient implements AutoCloseable { } /** See {@link ContentProvider#insert ContentProvider.insert} */ + @Override public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues) throws RemoteException { Preconditions.checkNotNull(url, "url"); @@ -295,6 +327,7 @@ public class ContentProviderClient implements AutoCloseable { } /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */ + @Override public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] initialValues) throws RemoteException { Preconditions.checkNotNull(url, "url"); @@ -314,6 +347,7 @@ public class ContentProviderClient implements AutoCloseable { } /** See {@link ContentProvider#delete ContentProvider.delete} */ + @Override public int delete(@NonNull Uri url, @Nullable String selection, @Nullable String[] selectionArgs) throws RemoteException { Preconditions.checkNotNull(url, "url"); @@ -332,6 +366,7 @@ public class ContentProviderClient implements AutoCloseable { } /** See {@link ContentProvider#update ContentProvider.update} */ + @Override public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) throws RemoteException { Preconditions.checkNotNull(url, "url"); @@ -368,6 +403,7 @@ public class ContentProviderClient implements AutoCloseable { * you use the {@link ContentResolver#openFileDescriptor * ContentResolver.openFileDescriptor} API instead. */ + @Override public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode, @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException { Preconditions.checkNotNull(url, "url"); @@ -411,6 +447,7 @@ public class ContentProviderClient implements AutoCloseable { * you use the {@link ContentResolver#openAssetFileDescriptor * ContentResolver.openAssetFileDescriptor} API instead. */ + @Override public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode, @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException { Preconditions.checkNotNull(url, "url"); @@ -446,8 +483,15 @@ public class ContentProviderClient implements AutoCloseable { public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri, @NonNull String mimeType, @Nullable Bundle opts, @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException { + return openTypedAssetFile(uri, mimeType, opts, signal); + } + + @Override + public final @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri, + @NonNull String mimeTypeFilter, @Nullable Bundle opts, + @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException { Preconditions.checkNotNull(uri, "uri"); - Preconditions.checkNotNull(mimeType, "mimeType"); + Preconditions.checkNotNull(mimeTypeFilter, "mimeTypeFilter"); beforeRemote(); try { @@ -458,7 +502,7 @@ public class ContentProviderClient implements AutoCloseable { signal.setRemote(remoteSignal); } return mContentProvider.openTypedAssetFile( - mPackageName, uri, mimeType, opts, remoteSignal); + mPackageName, uri, mimeTypeFilter, opts, remoteSignal); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); @@ -472,12 +516,20 @@ public class ContentProviderClient implements AutoCloseable { /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */ public @NonNull ContentProviderResult[] applyBatch( @NonNull ArrayList<ContentProviderOperation> operations) - throws RemoteException, OperationApplicationException { + throws RemoteException, OperationApplicationException { + return applyBatch(mAuthority, operations); + } + + /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */ + @Override + public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority, + @NonNull ArrayList<ContentProviderOperation> operations) + throws RemoteException, OperationApplicationException { Preconditions.checkNotNull(operations, "operations"); beforeRemote(); try { - return mContentProvider.applyBatch(mPackageName, operations); + return mContentProvider.applyBatch(mPackageName, authority, operations); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); @@ -491,11 +543,19 @@ public class ContentProviderClient implements AutoCloseable { /** See {@link ContentProvider#call(String, String, Bundle)} */ public @Nullable Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) throws RemoteException { + return call(mAuthority, method, arg, extras); + } + + /** See {@link ContentProvider#call(String, String, Bundle)} */ + @Override + public @Nullable Bundle call(@NonNull String authority, @NonNull String method, + @Nullable String arg, @Nullable Bundle extras) throws RemoteException { + Preconditions.checkNotNull(authority, "authority"); Preconditions.checkNotNull(method, "method"); beforeRemote(); try { - return mContentProvider.call(mPackageName, method, arg, extras); + return mContentProvider.call(mPackageName, authority, method, arg, extras); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index 6bede131c817..ca657b150f9c 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -174,13 +174,15 @@ abstract public class ContentProviderNative extends Binder implements IContentPr { data.enforceInterface(IContentProvider.descriptor); String callingPkg = data.readString(); + String authority = data.readString(); final int numOperations = data.readInt(); final ArrayList<ContentProviderOperation> operations = new ArrayList<>(numOperations); for (int i = 0; i < numOperations; i++) { operations.add(i, ContentProviderOperation.CREATOR.createFromParcel(data)); } - final ContentProviderResult[] results = applyBatch(callingPkg, operations); + final ContentProviderResult[] results = applyBatch(callingPkg, authority, + operations); reply.writeNoException(); reply.writeTypedArray(results, 0); return true; @@ -267,11 +269,12 @@ abstract public class ContentProviderNative extends Binder implements IContentPr data.enforceInterface(IContentProvider.descriptor); String callingPkg = data.readString(); + String authority = data.readString(); String method = data.readString(); String stringArg = data.readString(); Bundle args = data.readBundle(); - Bundle responseBundle = call(callingPkg, method, stringArg, args); + Bundle responseBundle = call(callingPkg, authority, method, stringArg, args); reply.writeNoException(); reply.writeBundle(responseBundle); @@ -507,7 +510,7 @@ final class ContentProviderProxy implements IContentProvider } @Override - public ContentProviderResult[] applyBatch(String callingPkg, + public ContentProviderResult[] applyBatch(String callingPkg, String authority, ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException { Parcel data = Parcel.obtain(); @@ -515,6 +518,7 @@ final class ContentProviderProxy implements IContentProvider try { data.writeInterfaceToken(IContentProvider.descriptor); data.writeString(callingPkg); + data.writeString(authority); data.writeInt(operations.size()); for (ContentProviderOperation operation : operations) { operation.writeToParcel(data, 0); @@ -636,14 +640,15 @@ final class ContentProviderProxy implements IContentProvider } @Override - public Bundle call(String callingPkg, String method, String request, Bundle args) - throws RemoteException { + public Bundle call(String callingPkg, String authority, String method, String request, + Bundle args) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(IContentProvider.descriptor); data.writeString(callingPkg); + data.writeString(authority); data.writeString(method); data.writeString(request); data.writeBundle(args); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 7d5202d0dbce..6704a45fb07a 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.annotation.UserIdInt; @@ -52,7 +53,6 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.storage.StorageManager; import android.text.TextUtils; @@ -88,7 +88,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> * developer guide.</p> */ -public abstract class ContentResolver { +public abstract class ContentResolver implements ContentInterface { /** * Enables logic that supports deprecation of {@code _data} columns, * typically by replacing values with fake paths that the OS then offers to @@ -655,6 +655,7 @@ public abstract class ContentResolver { * using the content:// scheme. * @return A MIME type for the content, or null if the URL is invalid or the type is unknown */ + @Override public final @Nullable String getType(@NonNull Uri url) { Preconditions.checkNotNull(url, "url"); @@ -708,6 +709,7 @@ public abstract class ContentResolver { * data streams that match the given mimeTypeFilter. If there are none, * null is returned. */ + @Override public @Nullable String[] getStreamTypes(@NonNull Uri url, @NonNull String mimeTypeFilter) { Preconditions.checkNotNull(url, "url"); Preconditions.checkNotNull(mimeTypeFilter, "mimeTypeFilter"); @@ -835,6 +837,7 @@ public abstract class ContentResolver { * @return A Cursor object, which is positioned before the first entry, or null * @see Cursor */ + @Override public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) { @@ -935,6 +938,7 @@ public abstract class ContentResolver { * * @see #uncanonicalize */ + @Override public final @Nullable Uri canonicalize(@NonNull Uri url) { Preconditions.checkNotNull(url, "url"); IContentProvider provider = acquireProvider(url); @@ -971,6 +975,7 @@ public abstract class ContentResolver { * * @see #canonicalize */ + @Override public final @Nullable Uri uncanonicalize(@NonNull Uri url) { Preconditions.checkNotNull(url, "url"); IContentProvider provider = acquireProvider(url); @@ -1005,6 +1010,7 @@ public abstract class ContentResolver { * canceled the refresh request. * @return true if the provider actually tried refreshing. */ + @Override public final boolean refresh(@NonNull Uri url, @Nullable Bundle args, @Nullable CancellationSignal cancellationSignal) { Preconditions.checkNotNull(url, "url"); @@ -1116,6 +1122,12 @@ public abstract class ContentResolver { } } + @Override + public final @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode, + @Nullable CancellationSignal signal) throws FileNotFoundException { + return openFileDescriptor(uri, mode, signal); + } + /** * Open a raw file descriptor to access data under a URI. This * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the @@ -1221,6 +1233,12 @@ public abstract class ContentResolver { throw new FileNotFoundException("Not a whole file"); } + @Override + public final @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode, + @Nullable CancellationSignal signal) throws FileNotFoundException { + return openAssetFileDescriptor(uri, mode, signal); + } + /** * Open a raw file descriptor to access data under a URI. This * interacts with the underlying {@link ContentProvider#openAssetFile} @@ -1425,6 +1443,13 @@ public abstract class ContentResolver { } } + @Override + public final @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri, + @NonNull String mimeTypeFilter, @Nullable Bundle opts, + @Nullable CancellationSignal signal) throws FileNotFoundException { + return openTypedAssetFileDescriptor(uri, mimeTypeFilter, opts, signal); + } + /** * Open a raw file descriptor to access (potentially type transformed) * data from a "content:" URI. This interacts with the underlying @@ -1634,6 +1659,7 @@ public abstract class ContentResolver { * the field. Passing an empty ContentValues will create an empty row. * @return the URL of the newly created row. */ + @Override public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url, @Nullable ContentValues values) { Preconditions.checkNotNull(url, "url"); @@ -1672,6 +1698,7 @@ public abstract class ContentResolver { * @throws RemoteException thrown if a RemoteException is encountered while attempting * to communicate with a remote provider. */ + @Override public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority, @NonNull ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException { @@ -1698,6 +1725,7 @@ public abstract class ContentResolver { * the field. Passing null will create an empty row. * @return the number of newly created rows. */ + @Override public final int bulkInsert(@RequiresPermission.Write @NonNull Uri url, @NonNull ContentValues[] values) { Preconditions.checkNotNull(url, "url"); @@ -1731,6 +1759,7 @@ public abstract class ContentResolver { (excluding the WHERE itself). * @return The number of rows deleted. */ + @Override public final int delete(@RequiresPermission.Write @NonNull Uri url, @Nullable String where, @Nullable String[] selectionArgs) { Preconditions.checkNotNull(url, "url"); @@ -1766,6 +1795,7 @@ public abstract class ContentResolver { * @return the number of rows updated. * @throws NullPointerException if uri or values are null */ + @Override public final int update(@RequiresPermission.Write @NonNull Uri uri, @Nullable ContentValues values, @Nullable String where, @Nullable String[] selectionArgs) { @@ -1805,14 +1835,20 @@ public abstract class ContentResolver { */ public final @Nullable Bundle call(@NonNull Uri uri, @NonNull String method, @Nullable String arg, @Nullable Bundle extras) { - Preconditions.checkNotNull(uri, "uri"); + return call(uri.getAuthority(), method, arg, extras); + } + + @Override + public final @Nullable Bundle call(@NonNull String authority, @NonNull String method, + @Nullable String arg, @Nullable Bundle extras) { + Preconditions.checkNotNull(authority, "authority"); Preconditions.checkNotNull(method, "method"); - IContentProvider provider = acquireProvider(uri); + IContentProvider provider = acquireProvider(authority); if (provider == null) { - throw new IllegalArgumentException("Unknown URI " + uri); + throw new IllegalArgumentException("Unknown authority " + authority); } try { - final Bundle res = provider.call(mPackageName, method, arg, extras); + final Bundle res = provider.call(mPackageName, authority, method, arg, extras); Bundle.setDefusable(res, true); return res; } catch (RemoteException e) { @@ -1918,7 +1954,7 @@ public abstract class ContentResolver { Preconditions.checkNotNull(uri, "uri"); IContentProvider provider = acquireProvider(uri); if (provider != null) { - return new ContentProviderClient(this, provider, true); + return new ContentProviderClient(this, provider, uri.getAuthority(), true); } return null; } @@ -1939,7 +1975,7 @@ public abstract class ContentResolver { Preconditions.checkNotNull(name, "name"); IContentProvider provider = acquireProvider(name); if (provider != null) { - return new ContentProviderClient(this, provider, true); + return new ContentProviderClient(this, provider, name, true); } return null; @@ -1966,7 +2002,7 @@ public abstract class ContentResolver { Preconditions.checkNotNull(uri, "uri"); IContentProvider provider = acquireUnstableProvider(uri); if (provider != null) { - return new ContentProviderClient(this, provider, false); + return new ContentProviderClient(this, provider, uri.getAuthority(), false); } return null; @@ -1993,7 +2029,7 @@ public abstract class ContentResolver { Preconditions.checkNotNull(name, "name"); IContentProvider provider = acquireUnstableProvider(name); if (provider != null) { - return new ContentProviderClient(this, provider, false); + return new ContentProviderClient(this, provider, name, false); } return null; @@ -2943,7 +2979,12 @@ public abstract class ContentResolver { } } - /** {@hide} */ + /** + * Put the cache with the key. + * + * @param key the key to add + * @param value the value to add + */ public void putCache(Uri key, Bundle value) { try { getContentService().putCache(mContext.getPackageName(), key, value, @@ -2953,7 +2994,13 @@ public abstract class ContentResolver { } } - /** {@hide} */ + /** + * Get the cache with the key. + * + * @param key the key to get the value + * @return the matched value. If the key doesn't exist, will return null. + * @see #putCache(Uri, Bundle) + */ public Bundle getCache(Uri key) { try { final Bundle bundle = getContentService().getCache(mContext.getPackageName(), key, @@ -3141,7 +3188,14 @@ public abstract class ContentResolver { return mContext.getUserId(); } - /** @hide */ + /** + * Get the system drawable of the mime type. + * + * @param mimeType the requested mime type + * @return the matched drawable + * @hide + */ + @SystemApi public Drawable getTypeDrawable(String mimeType) { return MimeIconUtils.loadMimeIcon(mContext, mimeType); } @@ -3248,10 +3302,10 @@ public abstract class ContentResolver { } /** {@hide} */ - public static Bitmap loadThumbnail(@NonNull ContentProviderClient client, @NonNull Uri uri, + public static Bitmap loadThumbnail(@NonNull ContentInterface content, @NonNull Uri uri, @NonNull Size size, @Nullable CancellationSignal signal, int allocator) throws IOException { - Objects.requireNonNull(client); + Objects.requireNonNull(content); Objects.requireNonNull(uri); Objects.requireNonNull(size); @@ -3260,7 +3314,7 @@ public abstract class ContentResolver { opts.putParcelable(EXTRA_SIZE, Point.convert(size)); return ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> { - return client.openTypedAssetFileDescriptor(uri, "image/*", opts, signal); + return content.openTypedAssetFile(uri, "image/*", opts, signal); }), (ImageDecoder decoder, ImageInfo info, Source source) -> { decoder.setAllocator(allocator); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index e2c7b85b3280..d7d3cb543af9 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3939,13 +3939,12 @@ public abstract class Context { public static final String AUTOFILL_MANAGER_SERVICE = "autofill"; /** - * Official published name of the smart suggestions service. + * Official published name of the content capture service. * * @hide * @see #getSystemService(String) */ - // TODO(b/111276913): rename string (will require SELinux change first) - public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "intelligence"; + public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture"; /** * Use with {@link #getSystemService(String)} to access the diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index 044ed61470ea..0427c2f52415 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -60,13 +60,28 @@ public interface IContentProvider extends IInterface { public AssetFileDescriptor openAssetFile( String callingPkg, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException; - public ContentProviderResult[] applyBatch(String callingPkg, + + @Deprecated + public default ContentProviderResult[] applyBatch(String callingPkg, + ArrayList<ContentProviderOperation> operations) + throws RemoteException, OperationApplicationException { + return applyBatch(callingPkg, "unknown", operations); + } + + public ContentProviderResult[] applyBatch(String callingPkg, String authority, ArrayList<ContentProviderOperation> operations) - throws RemoteException, OperationApplicationException; + throws RemoteException, OperationApplicationException; + + @Deprecated @UnsupportedAppUsage - public Bundle call( - String callingPkg, String method, @Nullable String arg, @Nullable Bundle extras) - throws RemoteException; + public default Bundle call(String callingPkg, String method, + @Nullable String arg, @Nullable Bundle extras) throws RemoteException { + return call(callingPkg, "unknown", method, arg, extras); + } + + public Bundle call(String callingPkg, String authority, String method, + @Nullable String arg, @Nullable Bundle extras) throws RemoteException; + public ICancellationSignal createCancellationSignal() throws RemoteException; public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index b6f9d15cc20b..2f0618cf64b3 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5267,8 +5267,6 @@ public class Intent implements Parcelable, Cloneable { * Used as a boolean extra field in {@link #ACTION_CHOOSER} intents to specify * whether to show the chooser or not when there is only one application available * to choose from. - * - * @hide */ public static final String EXTRA_AUTO_LAUNCH_SINGLE_CHOICE = "android.intent.extra.AUTO_LAUNCH_SINGLE_CHOICE"; diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 1c564f3708df..740fd7f963f9 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -16,6 +16,8 @@ package android.content.pm; import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; @@ -66,7 +68,30 @@ public class CrossProfileApps { mContext.getIApplicationThread(), mContext.getPackageName(), component, - targetUser); + targetUser.getIdentifier(), + true); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Starts the specified activity of the caller package in the specified profile if the caller + * has {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} permission and + * both the caller and target user profiles are in the same user group. + * + * @param component The ComponentName of the activity to launch. It must be exported. + * @param targetUser The UserHandle of the profile, must be one of the users returned by + * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will + * be thrown. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES) + public void startAnyActivity(@NonNull ComponentName component, @NonNull UserHandle targetUser) { + try { + mService.startActivityAsUser(mContext.getIApplicationThread(), + mContext.getPackageName(), component, targetUser.getIdentifier(), false); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl index bc2f92a9bf51..d2d66cbda610 100644 --- a/core/java/android/content/pm/ICrossProfileApps.aidl +++ b/core/java/android/content/pm/ICrossProfileApps.aidl @@ -28,6 +28,6 @@ import android.os.UserHandle; */ interface ICrossProfileApps { void startActivityAsUser(in IApplicationThread caller, in String callingPackage, - in ComponentName component, in UserHandle user); + in ComponentName component, int userId, boolean launchMainActivity); List<UserHandle> getTargetUserProfiles(in String callingPackage); }
\ No newline at end of file diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index ecc8cd678af1..a251c0036a78 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -42,6 +42,8 @@ interface IPackageInstaller { ParceledListSlice getAllSessions(int userId); ParceledListSlice getMySessions(String installerPackageName, int userId); + ParceledListSlice getStagedSessions(); + void registerCallback(IPackageInstallerCallback callback, int userId); void unregisterCallback(IPackageInstallerCallback callback); diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index cef21f607e5f..04e15c7957bd 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -38,9 +38,12 @@ interface IPackageInstallerSession { void commit(in IntentSender statusReceiver, boolean forTransferred); void transfer(in String packageName); void abandon(); + boolean isMultiPackage(); int[] getChildSessionIds(); void addChildSessionId(in int sessionId); void removeChildSessionId(in int sessionId); int getParentSessionId(); + + boolean isStaged(); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index dbea821fab2b..eea2b8873fe7 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -675,6 +675,8 @@ interface IPackageManager { String getSystemTextClassifierPackageName(); + String getWellbeingPackageName(); + boolean isPackageStateProtected(String packageName, int userId); void sendDeviceCustomizationReadyBroadcast(); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 07672d979cf5..96c30f156105 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -463,6 +463,18 @@ public class PackageInstaller { } /** + * Return list of all staged install sessions. + */ + public @NonNull List<SessionInfo> getStagedSessions() { + try { + // TODO: limit this to the mUserId? + return mInstaller.getStagedSessions().getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Uninstall the given package, removing it completely from the device. This * method is available to: * <ul> @@ -779,6 +791,11 @@ public class PackageInstaller { * individual session IDs can be added with {@link #addChildSessionId(int)} * and commit of the multi-package session will result in all child sessions * being committed atomically. + * <p> + * If a package requires to be installed only at reboot, the session should + * be marked as a staged session by calling {@link SessionParams#setStaged()} + * with {@code true}. This can also apply to a multi-package session, in + * which case all the packages in the session will be applied at reboot. */ public static class Session implements Closeable { /** {@hide} */ @@ -1105,6 +1122,17 @@ public class PackageInstaller { } /** + * @return {@code true} if this session will be staged and applied at next reboot. + */ + public boolean isStaged() { + try { + return mSession.isStaged(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @return the session ID of the multi-package session that this belongs to or * {@link SessionInfo#INVALID_ID} if it does not belong to a multi-package session. */ @@ -1227,6 +1255,8 @@ public class PackageInstaller { public String installerPackageName; /** {@hide} */ public boolean isMultiPackage; + /** {@hide} */ + public boolean isStaged; /** * Construct parameters for a new package install session. @@ -1257,6 +1287,7 @@ public class PackageInstaller { grantedRuntimePermissions = source.readStringArray(); installerPackageName = source.readString(); isMultiPackage = source.readBoolean(); + isStaged = source.readBoolean(); } /** @@ -1471,6 +1502,17 @@ public class PackageInstaller { this.isMultiPackage = true; } + /** + * Set this session to be staged to be installed at reboot. + * + * Staged sessions are scheduled to be installed at next reboot. Staged sessions can also be + * multi-package. In that case, if any of the children sessions fail to install at reboot, + * all the other children sessions are aborted as well. + */ + public void setStaged() { + this.isStaged = true; + } + /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); @@ -1488,6 +1530,7 @@ public class PackageInstaller { pw.printPair("grantedRuntimePermissions", grantedRuntimePermissions); pw.printPair("installerPackageName", installerPackageName); pw.printPair("isMultiPackage", isMultiPackage); + pw.printPair("isStaged", isStaged); pw.println(); } @@ -1514,6 +1557,7 @@ public class PackageInstaller { dest.writeStringArray(grantedRuntimePermissions); dest.writeString(installerPackageName); dest.writeBoolean(isMultiPackage); + dest.writeBoolean(isStaged); } public static final Parcelable.Creator<SessionParams> @@ -1593,6 +1637,8 @@ public class PackageInstaller { /** {@hide} */ public boolean isMultiPackage; /** {@hide} */ + public boolean isStaged; + /** {@hide} */ public int parentSessionId = INVALID_ID; /** {@hide} */ public int[] childSessionIds = NO_SESSIONS; @@ -1625,6 +1671,7 @@ public class PackageInstaller { grantedRuntimePermissions = source.readStringArray(); installFlags = source.readInt(); isMultiPackage = source.readBoolean(); + isStaged = source.readBoolean(); parentSessionId = source.readInt(); childSessionIds = source.createIntArray(); if (childSessionIds == null) { @@ -1892,6 +1939,13 @@ public class PackageInstaller { } /** + * Returns true if this session is a staged session which will be applied at next reboot. + */ + public boolean isStaged() { + return isStaged; + } + + /** * Returns the parent multi-package session ID if this session belongs to one, * {@link #INVALID_ID} otherwise. */ @@ -1935,6 +1989,7 @@ public class PackageInstaller { dest.writeStringArray(grantedRuntimePermissions); dest.writeInt(installFlags); dest.writeBoolean(isMultiPackage); + dest.writeBoolean(isStaged); dest.writeInt(parentSessionId); dest.writeIntArray(childSessionIds); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b7df2bf5a5f7..34cdfee3f959 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1399,6 +1399,16 @@ public abstract class PackageManager { public static final int DELETE_DONT_KILL_APP = 0x00000008; /** + * Flag parameter for {@link #deletePackage} to indicate that any + * contributed media should also be deleted during this uninstall. The + * meaning of "contributed" means it won't automatically be deleted when the + * app is uninstalled. + * + * @hide + */ + public static final int DELETE_CONTRIBUTED_MEDIA = 0x00000010; + + /** * Flag parameter for {@link #deletePackage} to indicate that package deletion * should be chatty. * @@ -2687,6 +2697,16 @@ public abstract class PackageManager { "android.software.device_id_attestation"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has + * the requisite kernel support for multinetworking-capable IPsec tunnels. + * + * <p>This feature implies that the device supports XFRM Interfaces (CONFIG_XFRM_INTERFACE), or + * VTIs with kernel patches allowing updates of output/set mark via UPDSA. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels"; + + /** * Extra field name for the URI to a verification file. Passed to a package * verifier. * @@ -3357,6 +3377,33 @@ public abstract class PackageManager { @ApplicationInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException; /** + * Retrieve all of the information we know about a particular + * package/application, for a specific user. + * + * @param packageName The full name (i.e. com.google.apps.contacts) of an + * application. + * @param flags Additional option flags to modify the data returned. + * @return An {@link ApplicationInfo} containing information about the + * package. If flag {@code MATCH_UNINSTALLED_PACKAGES} is set and if + * the package is not found in the list of installed applications, + * the application information is retrieved from the list of + * uninstalled applications (which includes installed applications + * as well as applications with data directory i.e. applications + * which had been deleted with {@code DONT_DELETE_DATA} flag set). + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + * @hide + */ + @NonNull + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) + @SystemApi + public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName, + @ApplicationInfoFlags int flags, @NonNull UserHandle user) + throws NameNotFoundException { + return getApplicationInfoAsUser(packageName, flags, user.getIdentifier()); + } + + /** * Retrieve all of the information we know about a particular activity * class. * @@ -4183,6 +4230,32 @@ public abstract class PackageManager { @ResolveInfoFlags int flags, @UserIdInt int userId); /** + * Retrieve all activities that can be performed for the given intent, for a + * specific user. + * + * @param intent The desired intent as per resolveActivity(). + * @param flags Additional option flags to modify the data returned. The + * most important is {@link #MATCH_DEFAULT_ONLY}, to limit the + * resolution to only those activities that support the + * {@link android.content.Intent#CATEGORY_DEFAULT}. Or, set + * {@link #MATCH_ALL} to prevent any filtering of the results. + * @param user The user being queried. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching activity, ordered from best to worst. In other + * words, the first item is what would be returned by + * {@link #resolveActivity}. If there are no matching activities, an + * empty list is returned. + * @hide + */ + @NonNull + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) + @SystemApi + public List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent, + @ResolveInfoFlags int flags, @NonNull UserHandle user) { + return queryIntentActivitiesAsUser(intent, flags, user.getIdentifier()); + } + + /** * Retrieve a set of activities that should be presented to the user as * similar options. This is like {@link #queryIntentActivities}, except it * also allows you to supply a list of more explicit Intents that you would @@ -4314,6 +4387,27 @@ public abstract class PackageManager { @ResolveInfoFlags int flags, @UserIdInt int userId); /** + * Retrieve all services that can match the given intent for a given user. + * + * @param intent The desired intent as per resolveService(). + * @param flags Additional option flags to modify the data returned. + * @param user The user being queried. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching service, ordered from best to worst. In other + * words, the first item is what would be returned by + * {@link #resolveService}. If there are no matching services, an + * empty list or null is returned. + * @hide + */ + @NonNull + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) + @SystemApi + public List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent, + @ResolveInfoFlags int flags, @NonNull UserHandle user) { + return queryIntentServicesAsUser(intent, flags, user.getIdentifier()); + } + + /** * Retrieve all providers that can match the given intent. * * @param intent An intent containing all of the desired specification @@ -4335,6 +4429,26 @@ public abstract class PackageManager { * @param intent An intent containing all of the desired specification * (action, data, type, category, and/or component). * @param flags Additional option flags to modify the data returned. + * @param user The user being queried. + * @return Returns a List of ResolveInfo objects containing one entry for + * each matching provider, ordered from best to worst. If there are + * no matching services, an empty list or null is returned. + * @hide + */ + @NonNull + @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) + @SystemApi + public List<ResolveInfo> queryIntentContentProvidersAsUser(@NonNull Intent intent, + @ResolveInfoFlags int flags, @NonNull UserHandle user) { + return queryIntentContentProvidersAsUser(intent, flags, user.getIdentifier()); + } + + /** + * Retrieve all providers that can match the given intent. + * + * @param intent An intent containing all of the desired specification + * (action, data, type, category, and/or component). + * @param flags Additional option flags to modify the data returned. * @return Returns a List of ResolveInfo objects containing one entry for * each matching provider, ordered from best to worst. If there are * no matching services, an empty list or null is returned. @@ -6424,6 +6538,17 @@ public abstract class PackageManager { } /** + * @return the wellbeing app package name, or null if it's not defined by the OEM. + * + * @hide + */ + @TestApi + public String getWellbeingPackageName() { + throw new UnsupportedOperationException( + "getWellbeingPackageName not implemented in subclass"); + } + + /** * @return whether a given package's state is protected, e.g. package cannot be disabled, * suspended, hidden or force stopped. * diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index b49c4476e82d..5db9f506e6e4 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -53,6 +53,8 @@ public abstract class PackageManagerInternal { public static final int PACKAGE_BROWSER = 4; public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 5; public static final int PACKAGE_PERMISSION_CONTROLLER = 6; + public static final int PACKAGE_WELLBEING = 7; + public static final int PACKAGE_DOCUMENTER = 8; @IntDef(value = { PACKAGE_SYSTEM, PACKAGE_SETUP_WIZARD, @@ -61,6 +63,8 @@ public abstract class PackageManagerInternal { PACKAGE_BROWSER, PACKAGE_SYSTEM_TEXT_CLASSIFIER, PACKAGE_PERMISSION_CONTROLLER, + PACKAGE_WELLBEING, + PACKAGE_DOCUMENTER, }) @Retention(RetentionPolicy.SOURCE) public @interface KnownPackage {} diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index ac18dca74950..d0de9a1d2a76 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -5380,6 +5380,10 @@ public class PackageParser { s.info.splitName = sa.getNonConfigurationString(R.styleable.AndroidManifestService_splitName, 0); + s.info.mForegroundServiceType = sa.getInt( + com.android.internal.R.styleable.AndroidManifestService_foregroundServiceType, + ServiceInfo.FOREGROUND_SERVICE_TYPE_UNSPECIFIED); + s.info.flags = 0; if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestService_stopWithTask, diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index d9d6b5f87eac..bb8c92dba71a 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -181,6 +181,28 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { @TestApi public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 0x10000; + /** + * Additional flag for {${link #protectionLevel}, corresponding + * to the <code>wellbeing</code> value of + * {@link android.R.attr#protectionLevel}. + * + * @hide + */ + @SystemApi + @TestApi + public static final int PROTECTION_FLAG_WELLBEING = 0x20000; + + /** + * Additional flag for {@link #protectionLevel}, corresponding to the + * {@code documenter} value of {@link android.R.attr#protectionLevel}. + * + * @hide + */ + @SystemApi + @TestApi + public static final int PROTECTION_FLAG_DOCUMENTER = 0x40000; + + /** @hide */ @IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = { PROTECTION_FLAG_PRIVILEGED, @@ -197,6 +219,8 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { PROTECTION_FLAG_OEM, PROTECTION_FLAG_VENDOR_PRIVILEGED, PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER, + PROTECTION_FLAG_WELLBEING, + PROTECTION_FLAG_DOCUMENTER, }) @Retention(RetentionPolicy.SOURCE) public @interface ProtectionFlags {} @@ -386,6 +410,12 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { if ((level & PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER) != 0) { protLevel += "|textClassifier"; } + if ((level & PermissionInfo.PROTECTION_FLAG_WELLBEING) != 0) { + protLevel += "|wellbeing"; + } + if ((level & PermissionInfo.PROTECTION_FLAG_DOCUMENTER) != 0) { + protLevel += "|documenter"; + } return protLevel; } diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index ad2c72274c07..8300c0c8d472 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -16,10 +16,14 @@ package android.content.pm; +import android.annotation.IntDef; import android.os.Parcel; import android.os.Parcelable; import android.util.Printer; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Information you can retrieve about a particular application * service. This corresponds to information collected from the @@ -94,6 +98,81 @@ public class ServiceInfo extends ComponentInfo */ public int flags; + /** + * The default foreground service type if not been set in manifest file. + */ + public static final int FOREGROUND_SERVICE_TYPE_UNSPECIFIED = 0; + + /** + * Constant corresponding to <code>sync</code> in + * the {@link android.R.attr#foregroundServiceType} attribute. + * Data(photo, file, account) upload/download, backup/restore, import/export, fetch, + * transfer over network between device and cloud. + */ + public static final int FOREGROUND_SERVICE_TYPE_SYNC = 1; + + /** + * Constant corresponding to <code>mediaPlay</code> in + * the {@link android.R.attr#foregroundServiceType} attribute. + * Music, video, news or other media play. + */ + public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAY = 2; + + /** + * Constant corresponding to <code>phoneCall</code> in + * the {@link android.R.attr#foregroundServiceType} attribute. + * Ongoing phone call or video conference. + */ + public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 3; + + /** + * Constant corresponding to <code>location</code> in + * the {@link android.R.attr#foregroundServiceType} attribute. + * GPS, map, navigation location update. + */ + public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 4; + + /** + * Constant corresponding to <code>deviceCompanion</code> in + * the {@link android.R.attr#foregroundServiceType} attribute. + * Auto, bluetooth, TV or other devices connection, monitoring and interaction. + */ + public static final int FOREGROUND_SERVICE_TYPE_DEVICE_COMPANION = 5; + + /** + * Constant corresponding to <code>ongoingProcess</code> in + * the {@link android.R.attr#foregroundServiceType} attribute. + * Process that should not be interrupted, including installation, setup, photo + * compression etc. + */ + public static final int FOREGROUND_SERVICE_TYPE_ONGOING_PROCESS = 6; + + /** + * The enumeration of values for foreground service type. + * The foreground service type is set in {@link android.R.attr#foregroundServiceType} + * attribute. + * @hide + */ + @IntDef(flag = false, prefix = { "FOREGROUND_SERVICE_TYPE_" }, value = { + FOREGROUND_SERVICE_TYPE_UNSPECIFIED, + FOREGROUND_SERVICE_TYPE_SYNC, + FOREGROUND_SERVICE_TYPE_MEDIA_PLAY, + FOREGROUND_SERVICE_TYPE_PHONE_CALL, + FOREGROUND_SERVICE_TYPE_LOCATION, + FOREGROUND_SERVICE_TYPE_DEVICE_COMPANION, + FOREGROUND_SERVICE_TYPE_ONGOING_PROCESS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ForegroundServiceType {} + + /** + * The type of foreground service, set in + * {@link android.R.attr#foregroundServiceType} attribute, one value in + * {@link ForegroundServiceType} + * @hide + */ + public @ForegroundServiceType int mForegroundServiceType = FOREGROUND_SERVICE_TYPE_UNSPECIFIED; + public ServiceInfo() { } @@ -101,6 +180,15 @@ public class ServiceInfo extends ComponentInfo super(orig); permission = orig.permission; flags = orig.flags; + mForegroundServiceType = orig.mForegroundServiceType; + } + + /** + * Return the current foreground service type. + * @return the current foreground service type. + */ + public int getForegroundServiceType() { + return mForegroundServiceType; } public void dump(Printer pw, String prefix) { diff --git a/core/java/android/content/pm/SharedLibraryNames.java b/core/java/android/content/pm/SharedLibraryNames.java index 5afc8a9721b4..a607a9ff682b 100644 --- a/core/java/android/content/pm/SharedLibraryNames.java +++ b/core/java/android/content/pm/SharedLibraryNames.java @@ -22,15 +22,15 @@ package android.content.pm; */ public class SharedLibraryNames { - public static final String ANDROID_HIDL_BASE = "android.hidl.base-V1.0-java"; + static final String ANDROID_HIDL_BASE = "android.hidl.base-V1.0-java"; - public static final String ANDROID_HIDL_MANAGER = "android.hidl.manager-V1.0-java"; + static final String ANDROID_HIDL_MANAGER = "android.hidl.manager-V1.0-java"; - public static final String ANDROID_TEST_BASE = "android.test.base"; + static final String ANDROID_TEST_BASE = "android.test.base"; - public static final String ANDROID_TEST_MOCK = "android.test.mock"; + static final String ANDROID_TEST_MOCK = "android.test.mock"; - public static final String ANDROID_TEST_RUNNER = "android.test.runner"; + static final String ANDROID_TEST_RUNNER = "android.test.runner"; public static final String ORG_APACHE_HTTP_LEGACY = "org.apache.http.legacy"; } diff --git a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl index 9490e276f228..2faf3adb34b6 100644 --- a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl +++ b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl @@ -22,6 +22,8 @@ import android.os.RemoteCallback; * Interface for communication with the permission presenter service. * * @hide + * @deprecated Only available to keep + * android.permissionpresenterservice.RuntimePermissionPresenterService functional */ oneway interface IRuntimePermissionPresenter { void getAppPermissions(String packageName, in RemoteCallback callback); diff --git a/core/java/android/content/pm/permission/RuntimePermissionPresentationInfo.java b/core/java/android/content/pm/permission/RuntimePermissionPresentationInfo.java index 352e8ad106b8..9fa863c75fdf 100644 --- a/core/java/android/content/pm/permission/RuntimePermissionPresentationInfo.java +++ b/core/java/android/content/pm/permission/RuntimePermissionPresentationInfo.java @@ -29,7 +29,11 @@ import android.os.Parcelable; * coarse and fine platform permissions. * * @hide + * + * @deprecated Not used anymore. Use {@link android.permission.RuntimePermissionPresentationInfo} + * instead */ +@Deprecated @SystemApi public final class RuntimePermissionPresentationInfo implements Parcelable { private static final int FLAG_GRANTED = 1 << 0; diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java index f220205b5f26..4fc7f5d257a9 100644 --- a/core/java/android/database/sqlite/SQLiteDebug.java +++ b/core/java/android/database/sqlite/SQLiteDebug.java @@ -143,7 +143,6 @@ public final class SQLiteDebug { /** * contains statistics about a database */ - @TestApi public static class DbStats { /** name of the database */ public String dbName; @@ -175,7 +174,6 @@ public final class SQLiteDebug { * return all pager and database stats for the current process. * @return {@link PagerStats} */ - @TestApi public static PagerStats getDatabaseInfo() { PagerStats stats = new PagerStats(); nativeGetPagerStats(stats); diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index 1d9330d4dfed..9d37d9939127 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -28,6 +28,21 @@ import android.hardware.face.FaceManager; */ public interface BiometricFaceConstants { // + // Accessibility constants + // + /** + * Require the user to look at the device during enrollment and + * authentication. Note this is to accommodate people who have limited + * vision. + */ + public static final int FEATURE_REQUIRE_ATTENTION = 1; + /** + * Require a diverse set of poses during enrollment. Note this is to + * accommodate people with limited mobility. + */ + public static final int FEATURE_REQUIRE_REQUIRE_DIVERSITY = 2; + + // // Error messages from face authentication hardware during initialization, enrollment, // authentication or removal. Must agree with the list in HAL h file // diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index ff58c7525a3a..eb3414d2ca48 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -152,5 +152,24 @@ public class BiometricManager { Slog.w(TAG, "setActiveUser(): Service not connected"); } } + + /** + * Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password) + * + * @param token an opaque token returned by password confirmation. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void resetTimeout(byte[] token) { + if (mService != null) { + try { + mService.resetTimeout(token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "resetTimeout(): Service not connected"); + } + } } diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 53a076135aaa..de828f2d6757 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -48,4 +48,7 @@ interface IBiometricService { // Notify BiometricService when <Biometric>Service is ready to start the prepared client. // Client lifecycle is still managed in <Biometric>Service. void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId); + + // Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password) + void resetTimeout(in byte [] token); } diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 9d61f028bc91..1af9cdebf6bc 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -16,6 +16,7 @@ package android.hardware.display; +import android.annotation.Nullable; import android.hardware.SensorManager; import android.os.Handler; import android.os.PowerManager; @@ -195,6 +196,44 @@ public abstract class DisplayManagerInternal { public abstract void onOverlayChanged(); /** + * Get the attributes available for display color sampling. + * @param displayId id of the display to collect the sample from. + * + * @return The attributes the display supports, or null if sampling is not supported. + */ + @Nullable + public abstract DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes( + int displayId); + + /** + * Enable or disable the collection of color samples. + * + * @param displayId id of the display to collect the sample from. + * @param componentMask a bitmask of the color channels to collect samples for, or zero for all + * available. + * @param maxFrames maintain a ringbuffer of the last maxFrames. + * @param enable True to enable, False to disable. + * + * @return True if sampling was enabled, false if failure. + */ + public abstract boolean setDisplayedContentSamplingEnabled( + int displayId, boolean enable, int componentMask, int maxFrames); + + /** + * Accesses the color histogram statistics of displayed frames on devices that support sampling. + * + * @param displayId id of the display to collect the sample from + * @param maxFrames limit the statistics to the last maxFrames number of frames. + * @param timestamp discard statistics that were collected prior to timestamp, where timestamp + * is given as CLOCK_MONOTONIC. + * @return The statistics representing a histogram of the color distribution of the frames + * displayed on-screen, or null if sampling is not supported. + */ + @Nullable + public abstract DisplayedContentSample getDisplayedContentSample( + int displayId, long maxFrames, long timestamp); + + /** * Describes the requested power state of the display. * * This object is intended to describe the general characteristics of the diff --git a/core/java/android/hardware/display/DisplayedContentSample.java b/core/java/android/hardware/display/DisplayedContentSample.java new file mode 100644 index 000000000000..0610377c648a --- /dev/null +++ b/core/java/android/hardware/display/DisplayedContentSample.java @@ -0,0 +1,94 @@ +/* + * Copyright 2018 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.hardware.display; + +/** + * @hide + */ +public final class DisplayedContentSample { + private long mNumFrames; + private long[] mSamplesComponent0; + private long[] mSamplesComponent1; + private long[] mSamplesComponent2; + private long[] mSamplesComponent3; + + /** + * Construct an object representing a color histogram of pixels that were displayed on screen. + * + * @param numFrames The number of frames represented by this sample. + * @param mSamplesComponent0 is a histogram counting how many times a pixel of a given value + * was displayed onscreen for FORMAT_COMPONENT_0. The buckets of the histogram are evenly + * weighted, the number of buckets is device specific. + * eg, for RGBA_8888, if sampleComponent0 is {10, 6, 4, 1} this means that 10 red pixels were + * displayed onscreen in range 0x00->0x3F, 6 red pixels were displayed onscreen in range + * 0x40->0x7F, etc. + * @param mSamplesComponent1 is the same sample definition as sampleComponent0, but for the + * second component of format. + * @param mSamplesComponent2 is the same sample definition as sampleComponent0, but for the + * third component of format. + * @param mSamplesComponent3 is the same sample definition as sampleComponent0, but for the + * fourth component of format. + */ + public DisplayedContentSample(long numFrames, + long[] sampleComponent0, + long[] sampleComponent1, + long[] sampleComponent2, + long[] sampleComponent3) { + mNumFrames = numFrames; + mSamplesComponent0 = sampleComponent0; + mSamplesComponent1 = sampleComponent1; + mSamplesComponent2 = sampleComponent2; + mSamplesComponent3 = sampleComponent3; + } + + public enum ColorComponent { + CHANNEL0, + CHANNEL1, + CHANNEL2, + CHANNEL3, + } + + /** + * Returns a color histogram according to component channel. + * + * @param component the component to return, according to the PixelFormat ordering + * (eg, for RGBA, CHANNEL0 is R, CHANNEL1 is G, etc). + * + * @return an evenly weighted histogram counting how many times a pixel was + * displayed onscreen that fell into the corresponding bucket, with the first entry + * corresponding to the normalized 0.0 value, and the last corresponding to the 1.0 + * value for that PixelFormat component. + */ + public long[] getSampleComponent(ColorComponent component) { + switch (component) { + case CHANNEL0: return mSamplesComponent0; + case CHANNEL1: return mSamplesComponent1; + case CHANNEL2: return mSamplesComponent2; + case CHANNEL3: return mSamplesComponent3; + default: throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Return the number of frames this sample was collected over. + * + * @return the number of frames that this sample was collected over. + */ + public long getNumFrames() { + return mNumFrames; + } +} diff --git a/core/java/android/hardware/display/DisplayedContentSamplingAttributes.java b/core/java/android/hardware/display/DisplayedContentSamplingAttributes.java new file mode 100644 index 000000000000..aad68d9bcf09 --- /dev/null +++ b/core/java/android/hardware/display/DisplayedContentSamplingAttributes.java @@ -0,0 +1,67 @@ +/* + * Copyright 2018 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.hardware.display; + +/** + * @hide + */ +public final class DisplayedContentSamplingAttributes { + private int mPixelFormat; + private int mDataspace; + private int mComponentMask; + + /* Creates the attributes reported by the display hardware about what capabilities + * are present. + * + * NOTE: the format and ds constants must match the values from graphics/common/x.x/types.hal + * @param format the format that the display hardware samples in. + * @param ds the dataspace in use when sampling. + * @param componentMask a mask of which of the format components are supported. + */ + public DisplayedContentSamplingAttributes(int format, int ds, int componentMask) { + mPixelFormat = format; + mDataspace = ds; + mComponentMask = componentMask; + } + + /* Returns the pixel format that the display hardware uses when sampling. + * + * NOTE: the returned constant matches the values from graphics/common/x.x/types.hal + * @return the format that the samples were collected in. + */ + public int getPixelFormat() { + return mPixelFormat; + } + + /* Returns the dataspace that the display hardware uses when sampling. + * + * NOTE: the returned constant matches the values from graphics/common/x.x/types.hal + * @return the dataspace that the samples were collected in. + */ + public int getDataspace() { + return mDataspace; + } + + /* Returns a mask of which components can be collected by the sampling engine. + * + * @return a mask of the components which are supported by the engine. The lowest + * bit corresponds to the lowest component (ie, 0x1 corresponds to A for RGBA). + */ + public int getComponentMask() { + return mComponentMask; + } +} diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 322863a6577d..bac23b3c00f9 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -207,11 +207,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) - public void enroll(byte[] token, CancellationSignal cancel, int flags, - int userId, EnrollmentCallback callback) { - if (userId == UserHandle.USER_CURRENT) { - userId = getCurrentUserId(); - } + public void enroll(byte[] token, CancellationSignal cancel, + EnrollmentCallback callback, int[] disabledFeatures) { if (callback == null) { throw new IllegalArgumentException("Must supply an enrollment callback"); } @@ -228,8 +225,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan if (mService != null) { try { mEnrollmentCallback = callback; - mService.enroll(mToken, token, userId, mServiceReceiver, flags, - mContext.getOpPackageName()); + mService.enroll(mToken, token, mServiceReceiver, + mContext.getOpPackageName(), disabledFeatures); } catch (RemoteException e) { Log.w(TAG, "Remote exception in enroll: ", e); if (callback != null) { @@ -284,10 +281,10 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) - public void setRequireAttention(boolean requireAttention, byte[] token) { + public void setFeature(int feature, boolean enabled, byte[] token) { if (mService != null) { try { - mService.setRequireAttention(requireAttention, token); + mService.setFeature(feature, enabled, token); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -298,11 +295,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) - public boolean getRequireAttention(byte[] token) { + public boolean getFeature(int feature) { boolean result = true; if (mService != null) { try { - mService.getRequireAttention(token); + result = mService.getFeature(feature); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index a15dcec3b276..a1c88f81e3e7 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -50,8 +50,8 @@ interface IFaceService { int callingUid, int callingPid, int callingUserId, boolean fromClient); // Start face enrollment - void enroll(IBinder token, in byte [] cryptoToken, int userId, IFaceServiceReceiver receiver, - int flags, String opPackageName); + void enroll(IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver, + String opPackageName, in int [] disabledFeatures); // Cancel enrollment in progress void cancelEnrollment(IBinder token); @@ -98,9 +98,9 @@ interface IFaceService { // Enumerate all faces void enumerate(IBinder token, int userId, IFaceServiceReceiver receiver); - int setRequireAttention(boolean requireAttention, in byte [] token); + int setFeature(int feature, boolean enabled, in byte [] token); - boolean getRequireAttention(in byte [] token); + boolean getFeature(int feature); void userActivity(); } diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 0bdfca7f5025..8a5f43de6883 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.IntDef; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; @@ -79,6 +80,7 @@ public final class NetworkCapabilities implements Parcelable { mNetworkCapabilities = mTransportTypes = mUnwantedNetworkCapabilities = 0; mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; mNetworkSpecifier = null; + mTransportInfo = null; mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; mUids = null; mEstablishingVpnAppUid = INVALID_UID; @@ -95,6 +97,7 @@ public final class NetworkCapabilities implements Parcelable { mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps; mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps; mNetworkSpecifier = nc.mNetworkSpecifier; + mTransportInfo = nc.mTransportInfo; mSignalStrength = nc.mSignalStrength; setUids(nc.mUids); // Will make the defensive copy mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid; @@ -874,6 +877,7 @@ public final class NetworkCapabilities implements Parcelable { } private NetworkSpecifier mNetworkSpecifier = null; + private TransportInfo mTransportInfo = null; /** * Sets the optional bearer specific network specifier. @@ -899,6 +903,19 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Sets the optional transport specific information. + * + * @param transportInfo A concrete, parcelable framework class that extends + * {@link TransportInfo}. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public NetworkCapabilities setTransportInfo(TransportInfo transportInfo) { + mTransportInfo = transportInfo; + return this; + } + + /** * Gets the optional bearer specific network specifier. * * @return The optional {@link NetworkSpecifier} specifying the bearer specific network @@ -910,6 +927,19 @@ public final class NetworkCapabilities implements Parcelable { return mNetworkSpecifier; } + /** + * Returns a transport-specific information container. The application may cast this + * container to a concrete sub-class based on its knowledge of the network request. The + * application should be able to deal with a {@code null} return value or an invalid case, + * e.g. use {@code instanceof} operator to verify expected type. + * + * @return A concrete implementation of the {@link TransportInfo} class or null if not + * available for the network. + */ + @Nullable public TransportInfo getTransportInfo() { + return mTransportInfo; + } + private void combineSpecifiers(NetworkCapabilities nc) { if (mNetworkSpecifier != null && !mNetworkSpecifier.equals(nc.mNetworkSpecifier)) { throw new IllegalStateException("Can't combine two networkSpecifiers"); @@ -926,6 +956,17 @@ public final class NetworkCapabilities implements Parcelable { return Objects.equals(mNetworkSpecifier, nc.mNetworkSpecifier); } + private void combineTransportInfos(NetworkCapabilities nc) { + if (mTransportInfo != null && !mTransportInfo.equals(nc.mTransportInfo)) { + throw new IllegalStateException("Can't combine two TransportInfos"); + } + setTransportInfo(nc.mTransportInfo); + } + + private boolean equalsTransportInfo(NetworkCapabilities nc) { + return Objects.equals(mTransportInfo, nc.mTransportInfo); + } + /** * Magic value that indicates no signal strength provided. A request specifying this value is * always satisfied. @@ -1238,6 +1279,7 @@ public final class NetworkCapabilities implements Parcelable { combineTransportTypes(nc); combineLinkBandwidths(nc); combineSpecifiers(nc); + combineTransportInfos(nc); combineSignalStrength(nc); combineUids(nc); combineSSIDs(nc); @@ -1347,6 +1389,7 @@ public final class NetworkCapabilities implements Parcelable { && equalsLinkBandwidths(that) && equalsSignalStrength(that) && equalsSpecifier(that) + && equalsTransportInfo(that) && equalsUids(that) && equalsSSID(that)); } @@ -1364,7 +1407,8 @@ public final class NetworkCapabilities implements Parcelable { + Objects.hashCode(mNetworkSpecifier) * 23 + (mSignalStrength * 29) + Objects.hashCode(mUids) * 31 - + Objects.hashCode(mSSID) * 37; + + Objects.hashCode(mSSID) * 37 + + Objects.hashCode(mTransportInfo) * 41; } @Override @@ -1379,6 +1423,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeInt(mLinkUpBandwidthKbps); dest.writeInt(mLinkDownBandwidthKbps); dest.writeParcelable((Parcelable) mNetworkSpecifier, flags); + dest.writeParcelable((Parcelable) mTransportInfo, flags); dest.writeInt(mSignalStrength); dest.writeArraySet(mUids); dest.writeString(mSSID); @@ -1396,6 +1441,7 @@ public final class NetworkCapabilities implements Parcelable { netCap.mLinkUpBandwidthKbps = in.readInt(); netCap.mLinkDownBandwidthKbps = in.readInt(); netCap.mNetworkSpecifier = in.readParcelable(null); + netCap.mTransportInfo = in.readParcelable(null); netCap.mSignalStrength = in.readInt(); netCap.mUids = (ArraySet<UidRange>) in.readArraySet( null /* ClassLoader, null for default */); @@ -1435,6 +1481,9 @@ public final class NetworkCapabilities implements Parcelable { if (mNetworkSpecifier != null) { sb.append(" Specifier: <").append(mNetworkSpecifier).append(">"); } + if (mTransportInfo != null) { + sb.append(" TransportInfo: <").append(mTransportInfo).append(">"); + } if (hasSignalStrength()) { sb.append(" SignalStrength: ").append(mSignalStrength); } @@ -1501,6 +1550,9 @@ public final class NetworkCapabilities implements Parcelable { if (mNetworkSpecifier != null) { proto.write(NetworkCapabilitiesProto.NETWORK_SPECIFIER, mNetworkSpecifier.toString()); } + if (mTransportInfo != null) { + // TODO b/120653863: write transport-specific info to proto? + } proto.write(NetworkCapabilitiesProto.CAN_REPORT_SIGNAL_STRENGTH, hasSignalStrength()); proto.write(NetworkCapabilitiesProto.SIGNAL_STRENGTH, mSignalStrength); diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 5447f595cad6..a00b9a3caeba 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -44,6 +44,7 @@ import java.util.Objects; * * @hide */ +// @NotThreadSafe public class NetworkStats implements Parcelable { private static final String TAG = "NetworkStats"; /** {@link #iface} value when interface details unavailable. */ @@ -443,6 +444,26 @@ public class NetworkStats implements Parcelable { return entry; } + /** + * If @{code dest} is not equal to @{code src}, copy entry from index @{code src} to index + * @{code dest}. + */ + private void maybeCopyEntry(int dest, int src) { + if (dest == src) return; + iface[dest] = iface[src]; + uid[dest] = uid[src]; + set[dest] = set[src]; + tag[dest] = tag[src]; + metered[dest] = metered[src]; + roaming[dest] = roaming[src]; + defaultNetwork[dest] = defaultNetwork[src]; + rxBytes[dest] = rxBytes[src]; + rxPackets[dest] = rxPackets[src]; + txBytes[dest] = txBytes[src]; + txPackets[dest] = txPackets[src]; + operations[dest] = operations[src]; + } + public long getElapsedRealtime() { return elapsedRealtime; } @@ -941,21 +962,18 @@ public class NetworkStats implements Parcelable { } /** - * Return all rows except those attributed to the requested UID; doesn't - * mutate the original structure. + * Remove all rows that match one of specified UIDs. */ - public NetworkStats withoutUids(int[] uids) { - final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); - - Entry entry = new Entry(); + public void removeUids(int[] uids) { + int nextOutputEntry = 0; for (int i = 0; i < size; i++) { - entry = getValues(i, entry); - if (!ArrayUtils.contains(uids, entry.uid)) { - stats.addValues(entry); + if (!ArrayUtils.contains(uids, uid[i])) { + maybeCopyEntry(nextOutputEntry, i); + nextOutputEntry++; } } - return stats; + size = nextOutputEntry; } /** diff --git a/core/java/android/net/PrivateDnsConnectivityChecker.java b/core/java/android/net/PrivateDnsConnectivityChecker.java new file mode 100644 index 000000000000..cfd458c65362 --- /dev/null +++ b/core/java/android/net/PrivateDnsConnectivityChecker.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018 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.net; + +import android.annotation.NonNull; +import android.util.Log; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +/** + * Class for testing connectivity to DNS-over-TLS servers. + * {@hide} + */ +public class PrivateDnsConnectivityChecker { + private static final String TAG = "NetworkUtils"; + + private static final int PRIVATE_DNS_PORT = 853; + private static final int CONNECTION_TIMEOUT_MS = 5000; + + private PrivateDnsConnectivityChecker() { } + + /** + * checks that a provided host can perform a TLS handshake on port 853. + * @param hostname host to connect to. + */ + public static boolean canConnectToPrivateDnsServer(@NonNull String hostname) { + final SocketFactory factory = SSLSocketFactory.getDefault(); + TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_APP); + + try (SSLSocket socket = (SSLSocket) factory.createSocket()) { + socket.setSoTimeout(CONNECTION_TIMEOUT_MS); + socket.connect(new InetSocketAddress(hostname, PRIVATE_DNS_PORT)); + if (!socket.isConnected()) { + Log.w(TAG, String.format("Connection to %s failed.", hostname)); + return false; + } + socket.startHandshake(); + Log.w(TAG, String.format("TLS handshake to %s succeeded.", hostname)); + return true; + } catch (IOException e) { + Log.w(TAG, String.format("TLS handshake to %s failed.", hostname), e); + return false; + } + } +} diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 22dd4fc362ba..bbf8f97c8865 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -274,7 +274,6 @@ public class TrafficStats { * Changes only take effect during subsequent calls to * {@link #tagSocket(Socket)}. */ - @SystemApi @SuppressLint("Doclava125") public static void setThreadStatsUid(int uid) { NetworkManagementSocketTagger.setThreadSocketStatsUid(uid); @@ -313,7 +312,6 @@ public class TrafficStats { * * @see #setThreadStatsUid(int) */ - @SystemApi @SuppressLint("Doclava125") public static void clearThreadStatsUid() { NetworkManagementSocketTagger.setThreadSocketStatsUid(-1); diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rHK/strings.xml b/core/java/android/net/TransportInfo.java index d5259d38ffa1..b78d3feccfa0 100644 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rHK/strings.xml +++ b/core/java/android/net/TransportInfo.java @@ -1,13 +1,11 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project +/* + * Copyright (C) 2018 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, @@ -15,9 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - --> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"精簡導覽列實驗"</string> -</resources> +package android.net; + +/** + * A container for transport-specific capabilities which is returned by + * {@link NetworkCapabilities#getTransportInfo()}. Specific networks + * may provide concrete implementations of this interface. + */ +public interface TransportInfo { +} diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index d09f33bcb755..af3ee0911d2f 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -374,11 +374,12 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { public abstract String toString(); /** - * Return a string representation of the URI that is safe to print - * to logs and other places where PII should be avoided. - * @hide + * Return a string representation of this URI that has common forms of PII redacted, + * making it safer to use for logging purposes. For example, {@code tel:800-466-4411} is + * returned as {@code tel:xxx-xxx-xxxx} and {@code http://example.com/path/to/item/} is + * returned as {@code http://example.com/...}. + * @return the common forms PII redacted string of this URI */ - @UnsupportedAppUsage public String toSafeString() { String scheme = getScheme(); String ssp = getSchemeSpecificPart(); diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java index f9b6dfce32ff..280dad0284b6 100644 --- a/core/java/android/net/http/X509TrustManagerExtensions.java +++ b/core/java/android/net/http/X509TrustManagerExtensions.java @@ -16,7 +16,6 @@ package android.net.http; -import android.annotation.SystemApi; import android.security.net.config.UserCertificateSource; import com.android.org.conscrypt.TrustManagerImpl; @@ -133,7 +132,6 @@ public class X509TrustManagerExtensions { * Returns {@code true} if the TrustManager uses the same trust configuration for the provided * hostnames. */ - @SystemApi public boolean isSameTrustConfiguration(String hostname1, String hostname2) { if (mIsSameTrustConfiguration == null) { return true; diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 2ae796c1ec56..d45fa11de639 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -916,23 +916,49 @@ public class Binder implements IBinder { private static native long getNativeBBinderHolder(); private static native long getFinalizer(); + /** + * By default, we use the calling uid since we can always trust it. + */ + private static volatile BinderInternal.WorkSourceProvider sWorkSourceProvider = + Binder::getCallingUid; + + /** + * Sets the work source provider. + * + * <li>The callback is global. Only fast operations should be done to avoid thread + * contentions. + * <li>The callback implementation needs to handle synchronization if needed. The methods on the + * callback can be called concurrently. + * <li>The callback is called on the critical path of the binder transaction so be careful about + * performance. + * <li>Never execute another binder transaction inside the callback. + * @hide + */ + public static void setWorkSourceProvider(BinderInternal.WorkSourceProvider workSourceProvider) { + if (workSourceProvider == null) { + throw new IllegalArgumentException("workSourceProvider cannot be null"); + } + sWorkSourceProvider = workSourceProvider; + } + // Entry point from android_util_Binder.cpp's onTransact private boolean execTransact(int code, long dataObj, long replyObj, int flags) { - final long origWorkSource = ThreadLocalWorkSource.setUid(Binder.getCallingUid()); + final int workSourceUid = sWorkSourceProvider.resolveWorkSourceUid(); + final long origWorkSource = ThreadLocalWorkSource.setUid(workSourceUid); try { - return execTransactInternal(code, dataObj, replyObj, flags); + return execTransactInternal(code, dataObj, replyObj, flags, workSourceUid); } finally { ThreadLocalWorkSource.restore(origWorkSource); } } private boolean execTransactInternal(int code, long dataObj, long replyObj, - int flags) { + int flags, int workSourceUid) { // Make sure the observer won't change while processing a transaction. final BinderInternal.Observer observer = sObserver; final CallSession callSession = - observer != null ? observer.callStarted(this, code) : null; + observer != null ? observer.callStarted(this, code, workSourceUid) : null; Parcel data = Parcel.obtain(dataObj); Parcel reply = Parcel.obtain(replyObj); // theoretically, we should call transact, which will call onTransact, @@ -971,10 +997,11 @@ public class Binder implements IBinder { if (tracingEnabled) { Trace.traceEnd(Trace.TRACE_TAG_ALWAYS); } + if (observer != null) { + observer.callEnded(callSession, data.dataSize(), reply.dataSize(), workSourceUid); + } } checkParcel(this, code, reply, "Unreasonably large binder reply buffer"); - int replySizeBytes = reply.dataSize(); - int requestSizeBytes = data.dataSize(); reply.recycle(); data.recycle(); @@ -984,9 +1011,6 @@ public class Binder implements IBinder { // to the main transaction loop to wait for another incoming transaction. Either // way, strict mode begone! StrictMode.clearGatheredViolations(); - if (observer != null) { - observer.callEnded(callSession, requestSizeBytes, replySizeBytes); - } return res; } } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 292543c4a19a..9fea873d34a8 100644..100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -1226,7 +1226,8 @@ public class Build { * null (if, for instance, the radio is not currently on). */ public static String getRadioVersion() { - return SystemProperties.get(TelephonyProperties.PROPERTY_BASEBAND_VERSION, null); + String propVal = SystemProperties.get(TelephonyProperties.PROPERTY_BASEBAND_VERSION); + return TextUtils.isEmpty(propVal) ? null : propVal; } private static String getString(String property) { diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 28ea553c8800..567efa7b7afb 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -66,6 +66,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Comparator; import java.util.Objects; @@ -717,6 +720,57 @@ public class FileUtils { } /** + * Compute the digest of the given file using the requested algorithm. + * + * @param algorithm Any valid algorithm accepted by + * {@link MessageDigest#getInstance(String)}. + * @hide + */ + public static byte[] digest(@NonNull File file, @NonNull String algorithm) + throws IOException, NoSuchAlgorithmException { + try (FileInputStream in = new FileInputStream(file)) { + return digest(in, algorithm); + } + } + + /** + * Compute the digest of the given file using the requested algorithm. + * + * @param algorithm Any valid algorithm accepted by + * {@link MessageDigest#getInstance(String)}. + * @hide + */ + public static byte[] digest(@NonNull InputStream in, @NonNull String algorithm) + throws IOException, NoSuchAlgorithmException { + // TODO: implement kernel optimizations + return digestInternalUserspace(in, algorithm); + } + + /** + * Compute the digest of the given file using the requested algorithm. + * + * @param algorithm Any valid algorithm accepted by + * {@link MessageDigest#getInstance(String)}. + * @hide + */ + public static byte[] digest(FileDescriptor fd, String algorithm) + throws IOException, NoSuchAlgorithmException { + // TODO: implement kernel optimizations + return digestInternalUserspace(new FileInputStream(fd), algorithm); + } + + private static byte[] digestInternalUserspace(InputStream in, String algorithm) + throws IOException, NoSuchAlgorithmException { + final MessageDigest digest = MessageDigest.getInstance(algorithm); + try (DigestInputStream digestStream = new DigestInputStream(in, digest)) { + final byte[] buffer = new byte[8192]; + while (digestStream.read(buffer) != -1) { + } + } + return digest.digest(); + } + + /** * Delete older files in a directory until only those matching the given * constraints remain. * diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 8cafbde8e3eb..7abe913312a1 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -16,14 +16,13 @@ package android.os; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.opengl.EGL14; -import android.os.Build; -import android.os.SystemProperties; import android.provider.Settings; import android.util.Log; @@ -37,6 +36,11 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** @hide */ public class GraphicsEnvironment { @@ -67,7 +71,7 @@ public class GraphicsEnvironment { */ public void setup(Context context, Bundle coreSettings) { setupGpuLayers(context, coreSettings); - setupAngle(context, coreSettings); + setupAngle(context, context.getPackageName()); chooseDriver(context, coreSettings); } @@ -192,24 +196,101 @@ public class GraphicsEnvironment { setLayerPaths(mClassLoader, layerPaths); } - /** - * Pass ANGLE details down to trigger enable logic - */ - private static void setupAngle(Context context, Bundle coreSettings) { + enum OpenGlDriverChoice { + DEFAULT, + NATIVE, + ANGLE + } + + private static final Map<OpenGlDriverChoice, String> sDriverMap = buildMap(); + private static Map<OpenGlDriverChoice, String> buildMap() { + Map<OpenGlDriverChoice, String> map = new HashMap<>(); + map.put(OpenGlDriverChoice.DEFAULT, "default"); + map.put(OpenGlDriverChoice.ANGLE, "angle"); + map.put(OpenGlDriverChoice.NATIVE, "native"); + + return map; + } + - String angleEnabledApp = - coreSettings.getString(Settings.Global.ANGLE_ENABLED_APP); + private static List<String> getGlobalSettingsString(Context context, String globalSetting) { + List<String> valueList = null; + ContentResolver contentResolver = context.getContentResolver(); + String settingsValue = Settings.Global.getString(contentResolver, globalSetting); - String packageName = context.getPackageName(); + if (settingsValue != null) { + valueList = new ArrayList<>(Arrays.asList(settingsValue.split(","))); + } else { + valueList = new ArrayList<>(); + } + + return valueList; + } + + private static int getGlobalSettingsPkgIndex(String pkgName, + List<String> globalSettingsDriverPkgs) { + for (int pkgIndex = 0; pkgIndex < globalSettingsDriverPkgs.size(); pkgIndex++) { + if (globalSettingsDriverPkgs.get(pkgIndex).equals(pkgName)) { + return pkgIndex; + } + } - boolean devOptIn = false; - if ((angleEnabledApp != null && packageName != null) - && (!angleEnabledApp.isEmpty() && !packageName.isEmpty()) - && angleEnabledApp.equals(packageName)) { + return -1; + } - Log.i(TAG, packageName + " opted in for ANGLE via Developer Setting"); + private static String getDriverForPkg(Context context, String packageName) { + try { + ContentResolver contentResolver = context.getContentResolver(); + int allUseAngle = Settings.Global.getInt(contentResolver, + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE); + if (allUseAngle == 1) { + return sDriverMap.get(OpenGlDriverChoice.ANGLE); + } + } catch (Settings.SettingNotFoundException e) { + // Do nothing and move on + } + + List<String> globalSettingsDriverPkgs = + getGlobalSettingsString(context, + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS); + List<String> globalSettingsDriverValues = + getGlobalSettingsString(context, + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES); + + // Make sure we have a good package name + if ((packageName == null) || (packageName.isEmpty())) { + return sDriverMap.get(OpenGlDriverChoice.DEFAULT); + } + // Make sure we have good settings to use + if (globalSettingsDriverPkgs.isEmpty() || globalSettingsDriverValues.isEmpty() + || (globalSettingsDriverPkgs.size() != globalSettingsDriverValues.size())) { + Log.w(TAG, + "Global.Settings values are invalid: " + + "globalSettingsDriverPkgs.size = " + + globalSettingsDriverPkgs.size() + ", " + + "globalSettingsDriverValues.size = " + + globalSettingsDriverValues.size()); + return sDriverMap.get(OpenGlDriverChoice.DEFAULT); + } + + int pkgIndex = getGlobalSettingsPkgIndex(packageName, globalSettingsDriverPkgs); + + if (pkgIndex < 0) { + return sDriverMap.get(OpenGlDriverChoice.DEFAULT); + } + + return globalSettingsDriverValues.get(pkgIndex); + } + + /** + * Pass ANGLE details down to trigger enable logic + */ + private void setupAngle(Context context, String packageName) { + String devOptIn = getDriverForPkg(context, packageName); - devOptIn = true; + if (DEBUG) { + Log.v(TAG, "ANGLE Developer option for '" + packageName + "' " + + "set to: '" + devOptIn + "'"); } ApplicationInfo angleInfo; @@ -303,8 +384,7 @@ public class GraphicsEnvironment { // Further opt-in logic is handled in native, so pass relevant info down // TODO: Move the ANGLE selection logic earlier so we don't need to keep these // file descriptors open. - setAngleInfo(paths, packageName, devOptIn, - rulesFd, rulesOffset, rulesLength); + setAngleInfo(paths, packageName, devOptIn, rulesFd, rulesOffset, rulesLength); } /** @@ -452,6 +532,6 @@ public class GraphicsEnvironment { private static native void setDebugLayersGLES(String layers); private static native void setDriverPath(String path); private static native void setAngleInfo(String path, String appPackage, - boolean devOptIn, FileDescriptor rulesFd, + String devOptIn, FileDescriptor rulesFd, long rulesOffset, long rulesLength); } diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java index 228fe7a3dae5..3de3494e7ea7 100644 --- a/core/java/android/os/HwBinder.java +++ b/core/java/android/os/HwBinder.java @@ -32,10 +32,7 @@ public abstract class HwBinder implements IHwBinder { /** * Create and initialize a HwBinder object and the native objects * used to allow this to participate in hwbinder transactions. - * - * @hide */ - @SystemApi public HwBinder() { native_setup(); @@ -44,7 +41,6 @@ public abstract class HwBinder implements IHwBinder { mNativeContext); } - /** @hide */ @Override public final native void transact( int code, HwParcel request, HwParcel reply, int flags) @@ -57,10 +53,7 @@ public abstract class HwBinder implements IHwBinder { * @param request parceled transaction * @param reply object to parcel reply into * @param flags transaction flags to be chosen by wire protocol - * - * @hide */ - @SystemApi public abstract void onTransact( int code, HwParcel request, HwParcel reply, int flags) throws RemoteException; @@ -69,9 +62,7 @@ public abstract class HwBinder implements IHwBinder { * Registers this service with the hwservicemanager. * * @param serviceName instance name of the service - * @hide */ - @SystemApi public native final void registerService(String serviceName) throws RemoteException; @@ -81,9 +72,7 @@ public abstract class HwBinder implements IHwBinder { * @param iface fully-qualified interface name for example foo.bar@1.3::IBaz * @param serviceName the instance name of the service for example default. * @throws NoSuchElementException when the service is unavailable - * @hide */ - @SystemApi public static final IHwBinder getService( String iface, String serviceName) @@ -96,9 +85,7 @@ public abstract class HwBinder implements IHwBinder { * @param serviceName the instance name of the service for example default. * @param retry whether to wait for the service to start if it's not already started * @throws NoSuchElementException when the service is unavailable - * @hide */ - @SystemApi public static native final IHwBinder getService( String iface, String serviceName, @@ -112,9 +99,7 @@ public abstract class HwBinder implements IHwBinder { * @param maxThreads total number of threads to create (includes this thread if * callerWillJoin is true) * @param callerWillJoin whether joinRpcThreadpool will be called in advance - * @hide */ - @SystemApi public static native final void configureRpcThreadpool( long maxThreads, boolean callerWillJoin); @@ -124,10 +109,7 @@ public abstract class HwBinder implements IHwBinder { * a threadpool with callerWillJoin true and then registering * the provided service if this thread doesn't need to do * anything else. - * - * @hide */ - @SystemApi public static native final void joinRpcThreadpool(); // Returns address of the "freeFunction". @@ -155,10 +137,7 @@ public abstract class HwBinder implements IHwBinder { * - tries to enable atracing (if enabled) * - tries to enable coverage dumps (if running in VTS) * - tries to enable record and replay (if running in VTS) - * - * @hide */ - @SystemApi public static void enableInstrumentation() { native_report_sysprop_change(); } diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java index fbdf27e38d67..249eb3aa3456 100644 --- a/core/java/android/os/IHwBinder.java +++ b/core/java/android/os/IHwBinder.java @@ -28,10 +28,7 @@ public interface IHwBinder { * @param request parceled transaction * @param reply object to parcel reply into * @param flags transaction flags to be chosen by wire protocol - * - * @hide */ - @SystemApi public void transact( int code, HwParcel request, HwParcel reply, int flags) throws RemoteException; @@ -40,23 +37,19 @@ public interface IHwBinder { * Return as IHwInterface instance only if this implements descriptor. * * @param descriptor for example foo.bar@1.0::IBaz - * @hide */ - @SystemApi public IHwInterface queryLocalInterface(String descriptor); /** * Interface for receiving a callback when the process hosting a service * has gone away. */ - @SystemApi public interface DeathRecipient { /** * Callback for a registered process dying. * * @param cookie cookie this death recipient was registered with. */ - @SystemApi public void serviceDied(long cookie); } @@ -67,13 +60,11 @@ public interface IHwBinder { * @param recipient callback object to be called on object death. * @param cookie value to be given to callback on object death. */ - @SystemApi public boolean linkToDeath(DeathRecipient recipient, long cookie); /** * Unregisters the death recipient from this binder. * * @param recipient callback to no longer recieve death notifications on this binder. */ - @SystemApi public boolean unlinkToDeath(DeathRecipient recipient); } diff --git a/core/java/android/os/IHwInterface.java b/core/java/android/os/IHwInterface.java index 1d9e2b0197c7..f9edd5bf8883 100644 --- a/core/java/android/os/IHwInterface.java +++ b/core/java/android/os/IHwInterface.java @@ -23,6 +23,5 @@ public interface IHwInterface { /** * @return the binder object that corresponds to this interface. */ - @SystemApi public IHwBinder asBinder(); } diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index c9c42058bad0..be8cf0e9137a 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -18,7 +18,6 @@ package android.os; import android.net.InterfaceConfiguration; -import android.net.INetd; import android.net.INetworkManagementEventObserver; import android.net.ITetheringStatsProvider; import android.net.Network; @@ -47,11 +46,6 @@ interface INetworkManagementService void unregisterObserver(INetworkManagementEventObserver obs); /** - * Retrieve an INetd to talk to netd. - */ - INetd getNetdService(); - - /** * Returns a list of currently known network interfaces */ String[] listInterfaces(); diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 2cb5aeeeb2e6..d55489a08136 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -200,4 +200,7 @@ public abstract class PowerManagerInternal { * PowerHint defined in android/hardware/power/<version 1.0 & up>/IPower.h */ public abstract void powerHint(int hintId, int data); + + /** Returns whether there hasn't been a user activity event for the given number of ms. */ + public abstract boolean wasDeviceIdleFor(long ms); } diff --git a/core/java/android/os/SystemUpdateManager.java b/core/java/android/os/SystemUpdateManager.java index ce3e225975f0..9146731f8f9f 100644 --- a/core/java/android/os/SystemUpdateManager.java +++ b/core/java/android/os/SystemUpdateManager.java @@ -34,62 +34,51 @@ public class SystemUpdateManager { private static final String TAG = "SystemUpdateManager"; /** The status key of the system update info, expecting an int value. */ - @SystemApi public static final String KEY_STATUS = "status"; /** The title of the current update, expecting a String value. */ - @SystemApi public static final String KEY_TITLE = "title"; /** Whether it is a security update, expecting a boolean value. */ - @SystemApi public static final String KEY_IS_SECURITY_UPDATE = "is_security_update"; /** The build fingerprint after installing the current update, expecting a String value. */ - @SystemApi public static final String KEY_TARGET_BUILD_FINGERPRINT = "target_build_fingerprint"; /** The security patch level after installing the current update, expecting a String value. */ - @SystemApi public static final String KEY_TARGET_SECURITY_PATCH_LEVEL = "target_security_patch_level"; /** * The KEY_STATUS value that indicates there's no update status info available. */ - @SystemApi public static final int STATUS_UNKNOWN = 0; /** * The KEY_STATUS value that indicates there's no pending update. */ - @SystemApi public static final int STATUS_IDLE = 1; /** * The KEY_STATUS value that indicates an update is available for download, but pending user * approval to start. */ - @SystemApi public static final int STATUS_WAITING_DOWNLOAD = 2; /** * The KEY_STATUS value that indicates an update is in progress (i.e. downloading or installing * has started). */ - @SystemApi public static final int STATUS_IN_PROGRESS = 3; /** * The KEY_STATUS value that indicates an update is available for install. */ - @SystemApi public static final int STATUS_WAITING_INSTALL = 4; /** * The KEY_STATUS value that indicates an update will be installed after a reboot. This applies * to both of A/B and non-A/B OTAs. */ - @SystemApi public static final int STATUS_WAITING_REBOOT = 5; private final ISystemUpdateManager mService; @@ -110,7 +99,6 @@ public class SystemUpdateManager { * * @throws SecurityException if the caller is not allowed to read the info. */ - @SystemApi @RequiresPermission(anyOf = { android.Manifest.permission.READ_SYSTEM_UPDATE_INFO, android.Manifest.permission.RECOVERY, @@ -137,7 +125,6 @@ public class SystemUpdateManager { * @throws IllegalArgumentException if @link #KEY_STATUS} does not exist. * @throws SecurityException if the caller is not allowed to update the info. */ - @SystemApi @RequiresPermission(android.Manifest.permission.RECOVERY) public void updateSystemUpdateInfo(PersistableBundle infoBundle) { if (infoBundle == null || !infoBundle.containsKey(KEY_STATUS)) { diff --git a/core/java/android/os/Temperature.java b/core/java/android/os/Temperature.java index 5499181c3cdb..eee2b520afa8 100644 --- a/core/java/android/os/Temperature.java +++ b/core/java/android/os/Temperature.java @@ -70,6 +70,7 @@ public final class Temperature implements Parcelable { TYPE_BCL_VOLTAGE, TYPE_BCL_CURRENT, TYPE_BCL_PERCENTAGE, + TYPE_NPU, }) @Retention(RetentionPolicy.SOURCE) public @interface Type {} @@ -85,6 +86,7 @@ public final class Temperature implements Parcelable { public static final int TYPE_BCL_VOLTAGE = TemperatureType.BCL_VOLTAGE; public static final int TYPE_BCL_CURRENT = TemperatureType.BCL_CURRENT; public static final int TYPE_BCL_PERCENTAGE = TemperatureType.BCL_PERCENTAGE; + public static final int TYPE_NPU = TemperatureType.NPU; /** * Verify a valid temperature type. @@ -92,7 +94,7 @@ public final class Temperature implements Parcelable { * @return true if a temperature type is valid otherwise false. */ public static boolean isValidType(@Type int type) { - return type >= TYPE_UNKNOWN && type <= TYPE_BCL_PERCENTAGE; + return type >= TYPE_UNKNOWN && type <= TYPE_NPU; } /** diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java index 24c9c9177360..8f2826c16b63 100644 --- a/core/java/android/os/UpdateEngine.java +++ b/core/java/android/os/UpdateEngine.java @@ -54,7 +54,6 @@ public class UpdateEngine { * Error code from the update engine. Values must agree with the ones in * system/update_engine/common/error_code.h. */ - @SystemApi public static final class ErrorCodeConstants { public static final int SUCCESS = 0; public static final int ERROR = 1; @@ -74,7 +73,6 @@ public class UpdateEngine { * Update status code from the update engine. Values must agree with the * ones in system/update_engine/client_library/include/update_engine/update_status.h. */ - @SystemApi public static final class UpdateStatusConstants { public static final int IDLE = 0; public static final int CHECKING_FOR_UPDATE = 1; @@ -95,7 +93,6 @@ public class UpdateEngine { /** * Creates a new instance. */ - @SystemApi public UpdateEngine() { mUpdateEngine = IUpdateEngine.Stub.asInterface( ServiceManager.getService(UPDATE_ENGINE_SERVICE)); @@ -106,7 +103,6 @@ public class UpdateEngine { * status change, and when the update completes. A handler can be supplied * to control which thread runs the callback, or null. */ - @SystemApi public boolean bind(final UpdateEngineCallback callback, final Handler handler) { synchronized (mUpdateEngineCallbackLock) { mUpdateEngineCallback = new IUpdateEngineCallback.Stub() { @@ -150,7 +146,6 @@ public class UpdateEngine { /** * Equivalent to {@code bind(callback, null)}. */ - @SystemApi public boolean bind(final UpdateEngineCallback callback) { return bind(callback, null); } @@ -183,7 +178,6 @@ public class UpdateEngine { * }; * </pre> */ - @SystemApi public void applyPayload(String url, long offset, long size, String[] headerKeyValuePairs) { try { mUpdateEngine.applyPayload(url, offset, size, headerKeyValuePairs); @@ -201,7 +195,6 @@ public class UpdateEngine { * <p>See {@link #suspend} for a way to temporarily stop an in-progress * update with the ability to resume it later. */ - @SystemApi public void cancel() { try { mUpdateEngine.cancel(); @@ -214,7 +207,6 @@ public class UpdateEngine { * Suspends an in-progress update. This can be undone by calling * {@link #resume}. */ - @SystemApi public void suspend() { try { mUpdateEngine.suspend(); @@ -226,7 +218,6 @@ public class UpdateEngine { /** * Resumes a suspended update. */ - @SystemApi public void resume() { try { mUpdateEngine.resume(); @@ -244,7 +235,6 @@ public class UpdateEngine { * {@code UPDATED_NEED_REBOOT}, so your callback can remove any outstanding * notification that rebooting into the new system is possible. */ - @SystemApi public void resetStatus() { try { mUpdateEngine.resetStatus(); @@ -256,7 +246,6 @@ public class UpdateEngine { /** * Unbinds the last bound callback function. */ - @SystemApi public boolean unbind() { synchronized (mUpdateEngineCallbackLock) { if (mUpdateEngineCallback == null) { @@ -281,7 +270,6 @@ public class UpdateEngine { * @param payloadMetadataFilename the location of the metadata without the * {@code file://} prefix. */ - @SystemApi public boolean verifyPayloadMetadata(String payloadMetadataFilename) { try { return mUpdateEngine.verifyPayloadApplicable(payloadMetadataFilename); diff --git a/core/java/android/os/UpdateEngineCallback.java b/core/java/android/os/UpdateEngineCallback.java index afff60abb22b..f07294e222e2 100644 --- a/core/java/android/os/UpdateEngineCallback.java +++ b/core/java/android/os/UpdateEngineCallback.java @@ -37,7 +37,6 @@ public abstract class UpdateEngineCallback { * be one of the values from {@link UpdateEngine.UpdateStatusConstants}, * and {@code percent} will be valid [TODO: in which cases?]. */ - @SystemApi public abstract void onStatusUpdate(int status, float percent); /** @@ -45,6 +44,5 @@ public abstract class UpdateEngineCallback { * unsuccessfully. The value of {@code errorCode} will be one of the * values from {@link UpdateEngine.ErrorCodeConstants}. */ - @SystemApi public abstract void onPayloadApplicationComplete(int errorCode); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index abfcfaacde21..17ce79b83aa8 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -948,7 +948,7 @@ public class UserManager { * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ - public static final String DISALLOW_INTELLIGENCE_CAPTURE = "no_intelligence_capture"; + public static final String DISALLOW_CONTENT_CAPTURE = "no_content_capture"; /** * Specifies if user switching is blocked on the current user. @@ -1225,6 +1225,9 @@ public class UserManager { * system user hasn't been unlocked yet, or {@link #DISALLOW_USER_SWITCH} is set. * @hide */ + @SystemApi + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) public boolean canSwitchUsers() { boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt( mContext.getContentResolver(), @@ -2646,6 +2649,19 @@ public class UserManager { } /** + * Removes a user and all associated data. + * + * @param user the user that needs to be removed. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public boolean removeUser(UserHandle user) { + return removeUser(user.getIdentifier()); + } + + + /** * Similar to {@link #removeUser(int)} except bypassing the checking of * {@link UserManager#DISALLOW_REMOVE_USER} * or {@link UserManager#DISALLOW_REMOVE_MANAGED_PROFILE}. diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index bf988ae59299..f114b12cc3d1 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -190,4 +190,5 @@ interface IStorageManager { void abortIdleMaintenance() = 80; String translateAppToSystem(String path, int pid, int uid) = 81; String translateSystemToApp(String path, int pid, int uid) = 82; + void commitChanges() = 83; } diff --git a/core/java/android/permission/IRuntimePermissionPresenter.aidl b/core/java/android/permission/IRuntimePermissionPresenter.aidl new file mode 100644 index 000000000000..e95428ab2b02 --- /dev/null +++ b/core/java/android/permission/IRuntimePermissionPresenter.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.permission; + +import android.os.RemoteCallback; + +/** + * Interface for communication with the permission presenter service. + * + * @hide + */ +oneway interface IRuntimePermissionPresenter { + void getAppPermissions(String packageName, in RemoteCallback callback); + void revokeRuntimePermission(String packageName, String permissionName); + void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted, + boolean countSystem, in RemoteCallback callback); +} diff --git a/core/java/android/content/pm/permission/RuntimePermissionPresentationInfo.aidl b/core/java/android/permission/RuntimePermissionPresentationInfo.aidl index f96a32f68bd8..533c1ffc3bb8 100644 --- a/core/java/android/content/pm/permission/RuntimePermissionPresentationInfo.aidl +++ b/core/java/android/permission/RuntimePermissionPresentationInfo.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.content.pm.permission; +package android.permission; parcelable RuntimePermissionPresentationInfo; diff --git a/core/java/android/permission/RuntimePermissionPresentationInfo.java b/core/java/android/permission/RuntimePermissionPresentationInfo.java new file mode 100644 index 000000000000..ed7b05c83046 --- /dev/null +++ b/core/java/android/permission/RuntimePermissionPresentationInfo.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.permission; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class contains information about how a runtime permission + * is to be presented in the UI. A single runtime permission + * presented to the user may correspond to multiple platform defined + * permissions, e.g. the location permission may control both the + * coarse and fine platform permissions. + * + * @hide + */ +@SystemApi +public final class RuntimePermissionPresentationInfo implements Parcelable { + private static final int FLAG_GRANTED = 1 << 0; + private static final int FLAG_STANDARD = 1 << 1; + + private final CharSequence mLabel; + private final int mFlags; + + /** + * Creates a new instance. + * + * @param label The permission label. + * @param granted Whether the permission is granted. + * @param standard Whether this is a platform-defined permission. + */ + public RuntimePermissionPresentationInfo(CharSequence label, + boolean granted, boolean standard) { + mLabel = label; + int flags = 0; + if (granted) { + flags |= FLAG_GRANTED; + } + if (standard) { + flags |= FLAG_STANDARD; + } + mFlags = flags; + } + + private RuntimePermissionPresentationInfo(Parcel parcel) { + mLabel = parcel.readCharSequence(); + mFlags = parcel.readInt(); + } + + /** + * @return Whether the permission is granted. + */ + public boolean isGranted() { + return (mFlags & FLAG_GRANTED) != 0; + } + + /** + * @return Whether the permission is platform-defined. + */ + public boolean isStandard() { + return (mFlags & FLAG_STANDARD) != 0; + } + + /** + * Gets the permission label. + * + * @return The label. + */ + public @NonNull CharSequence getLabel() { + return mLabel; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeCharSequence(mLabel); + parcel.writeInt(mFlags); + } + + public static final Creator<RuntimePermissionPresentationInfo> CREATOR = + new Creator<RuntimePermissionPresentationInfo>() { + public RuntimePermissionPresentationInfo createFromParcel(Parcel source) { + return new RuntimePermissionPresentationInfo(source); + } + + public RuntimePermissionPresentationInfo[] newArray(int size) { + return new RuntimePermissionPresentationInfo[size]; + } + }; +} diff --git a/core/java/android/content/pm/permission/RuntimePermissionPresenter.java b/core/java/android/permission/RuntimePermissionPresenter.java index 73addb70ea05..3ce3c9db2623 100644 --- a/core/java/android/content/pm/permission/RuntimePermissionPresenter.java +++ b/core/java/android/permission/RuntimePermissionPresenter.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package android.content.pm.permission; +package android.permission; +import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -30,7 +31,6 @@ import android.os.IBinder; import android.os.Message; import android.os.RemoteCallback; import android.os.RemoteException; -import android.permissionpresenterservice.RuntimePermissionPresenterService; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -60,20 +60,32 @@ public final class RuntimePermissionPresenter { * @hide */ public static final String KEY_RESULT = - "android.content.pm.permission.RuntimePermissionPresenter.key.result"; + "android.permission.RuntimePermissionPresenter.key.result"; /** - * Listener for delivering a result. + * Listener for delivering the result of {@link #getAppPermissions}. */ - public static abstract class OnResultCallback { + public interface OnGetAppPermissionResultCallback { /** - * The result for {@link #getAppPermissions(String, OnResultCallback, Handler)}. + * The result for {@link #getAppPermissions(String, OnGetAppPermissionResultCallback, + * Handler)}. + * * @param permissions The permissions list. */ - public void onGetAppPermissions(@NonNull - List<RuntimePermissionPresentationInfo> permissions) { - /* do nothing - stub */ - } + void onGetAppPermissions(@NonNull List<RuntimePermissionPresentationInfo> permissions); + } + + /** + * Listener for delivering the result of {@link #countPermissionApps}. + */ + public interface OnCountPermissionAppsResultCallback { + /** + * The result for {@link #countPermissionApps(List, boolean, + * OnCountPermissionAppsResultCallback, Handler)}. + * + * @param numApps The number of apps that have one of the permissions + */ + void onCountPermissionApps(int numApps); } private static final Object sLock = new Object(); @@ -110,7 +122,7 @@ public final class RuntimePermissionPresenter { * @param handler Handler on which to invoke the callback. */ public void getAppPermissions(@NonNull String packageName, - @NonNull OnResultCallback callback, @Nullable Handler handler) { + @NonNull OnGetAppPermissionResultCallback callback, @Nullable Handler handler) { checkNotNull(packageName); checkNotNull(callback); @@ -133,6 +145,25 @@ public final class RuntimePermissionPresenter { mRemoteService, packageName, permissionName)); } + /** + * Count how many apps have one of a set of permissions. + * + * @param permissionNames The permissions the app might have + * @param countOnlyGranted Count an app only if the permission is granted to the app + * @param countSystem Also count system apps + * @param callback Callback to receive the result + * @param handler Handler on which to invoke the callback + */ + public void countPermissionApps(@NonNull List<String> permissionNames, + boolean countOnlyGranted, boolean countSystem, + @NonNull OnCountPermissionAppsResultCallback callback, @Nullable Handler handler) { + checkCollectionElementsNotNull(permissionNames, "permissionNames"); + checkNotNull(callback); + + mRemoteService.processMessage(obtainMessage(RemoteService::countPermissionApps, + mRemoteService, permissionNames, countOnlyGranted, countSystem, callback, handler)); + } + private static final class RemoteService extends Handler implements ServiceConnection { private static final long UNBIND_TIMEOUT_MILLIS = 10000; @@ -152,7 +183,7 @@ public final class RuntimePermissionPresenter { @GuardedBy("mLock") private boolean mBound; - public RemoteService(Context context) { + RemoteService(Context context) { super(context.getMainLooper(), null, false); mContext = context; } @@ -188,7 +219,7 @@ public final class RuntimePermissionPresenter { } private void getAppPermissions(@NonNull String packageName, - @NonNull OnResultCallback callback, @Nullable Handler handler) { + @NonNull OnGetAppPermissionResultCallback callback, @Nullable Handler handler) { final IRuntimePermissionPresenter remoteInstance; synchronized (mLock) { remoteInstance = mRemoteInstance; @@ -245,6 +276,45 @@ public final class RuntimePermissionPresenter { } } + private void countPermissionApps(@NonNull List<String> permissionNames, + boolean countOnlyGranted, boolean countSystem, + @NonNull OnCountPermissionAppsResultCallback callback, @Nullable Handler handler) { + final IRuntimePermissionPresenter remoteInstance; + + synchronized (mLock) { + remoteInstance = mRemoteInstance; + } + if (remoteInstance == null) { + return; + } + + try { + remoteInstance.countPermissionApps(permissionNames, countOnlyGranted, countSystem, + new RemoteCallback(result -> { + final int numApps; + if (result != null) { + numApps = result.getInt(KEY_RESULT); + } else { + numApps = 0; + } + + if (handler != null) { + handler.post(() -> callback.onCountPermissionApps(numApps)); + } else { + callback.onCountPermissionApps(numApps); + } + }, this)); + } catch (RemoteException re) { + Log.e(TAG, "Error counting permission apps", re); + } + + scheduleUnbind(); + + synchronized (mLock) { + scheduleNextMessageIfNeededLocked(); + } + } + private void unbind() { synchronized (mLock) { if (mBound) { diff --git a/core/java/android/permission/RuntimePermissionPresenterService.java b/core/java/android/permission/RuntimePermissionPresenterService.java new file mode 100644 index 000000000000..81ec7bed19af --- /dev/null +++ b/core/java/android/permission/RuntimePermissionPresenterService.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.permission; + +import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; +import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteCallback; + +import java.util.List; + +/** + * This service presents information regarding runtime permissions that is + * used for presenting them in the UI. Runtime permissions are presented as + * a single permission in the UI but may be composed of several individual + * permissions. + * + * @see RuntimePermissionPresenter + * @see RuntimePermissionPresentationInfo + * + * @hide + */ +@SystemApi +public abstract class RuntimePermissionPresenterService extends Service { + + /** + * The {@link Intent} action that must be declared as handled by a service + * in its manifest for the system to recognize it as a runtime permission + * presenter service. + */ + public static final String SERVICE_INTERFACE = + "android.permission.RuntimePermissionPresenterService"; + + // No need for locking - always set first and never modified + private Handler mHandler; + + @Override + public final void attachBaseContext(Context base) { + super.attachBaseContext(base); + mHandler = new Handler(base.getMainLooper()); + } + + /** + * Gets the runtime permissions for an app. + * + * @param packageName The package for which to query. + * + * @return descriptions of the runtime permissions of the app + */ + public abstract @NonNull List<RuntimePermissionPresentationInfo> onGetAppPermissions( + @NonNull String packageName); + + /** + * Revokes the permission {@code permissionName} for app {@code packageName} + * + * @param packageName The package for which to revoke + * @param permissionName The permission to revoke + */ + public abstract void onRevokeRuntimePermission(@NonNull String packageName, + @NonNull String permissionName); + + /** + * Count how many apps have one of a set of permissions. + * + * @param permissionNames The permissions the app might have + * @param countOnlyGranted Count an app only if the permission is granted to the app + * @param countSystem Also count system apps + * + * @return the number of apps that have one of the permissions + */ + public abstract int onCountPermissionApps(@NonNull List<String> permissionNames, + boolean countOnlyGranted, boolean countSystem); + + @Override + public final IBinder onBind(Intent intent) { + return new IRuntimePermissionPresenter.Stub() { + @Override + public void getAppPermissions(String packageName, RemoteCallback callback) { + checkNotNull(packageName, "packageName"); + checkNotNull(callback, "callback"); + + mHandler.sendMessage( + obtainMessage( + RuntimePermissionPresenterService::getAppPermissions, + RuntimePermissionPresenterService.this, packageName, callback)); + } + + @Override + public void revokeRuntimePermission(String packageName, String permissionName) { + checkNotNull(packageName, "packageName"); + checkNotNull(permissionName, "permissionName"); + + mHandler.sendMessage( + obtainMessage( + RuntimePermissionPresenterService::onRevokeRuntimePermission, + RuntimePermissionPresenterService.this, packageName, + permissionName)); + } + + @Override + public void countPermissionApps(List<String> permissionNames, boolean countOnlyGranted, + boolean countSystem, RemoteCallback callback) { + checkCollectionElementsNotNull(permissionNames, "permissionNames"); + checkNotNull(callback, "callback"); + + mHandler.sendMessage( + obtainMessage( + RuntimePermissionPresenterService::countPermissionApps, + RuntimePermissionPresenterService.this, permissionNames, + countOnlyGranted, countSystem, callback)); + } + }; + } + + private void getAppPermissions(@NonNull String packageName, @NonNull RemoteCallback callback) { + List<RuntimePermissionPresentationInfo> permissions = onGetAppPermissions(packageName); + if (permissions != null && !permissions.isEmpty()) { + Bundle result = new Bundle(); + result.putParcelableList(RuntimePermissionPresenter.KEY_RESULT, permissions); + callback.sendResult(result); + } else { + callback.sendResult(null); + } + } + + private void countPermissionApps(@NonNull List<String> permissionNames, + boolean countOnlyGranted, boolean countSystem, @NonNull RemoteCallback callback) { + int numApps = onCountPermissionApps(permissionNames, countOnlyGranted, countSystem); + + Bundle result = new Bundle(); + result.putInt(RuntimePermissionPresenter.KEY_RESULT, numApps); + callback.sendResult(result); + } +} diff --git a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java index a41a64422038..2b3f0f525904 100644 --- a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java +++ b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java @@ -26,7 +26,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.permission.IRuntimePermissionPresenter; import android.content.pm.permission.RuntimePermissionPresentationInfo; -import android.content.pm.permission.RuntimePermissionPresenter; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -40,11 +39,13 @@ import java.util.List; * a single permission in the UI but may be composed of several individual * permissions. * - * @see RuntimePermissionPresenter * @see RuntimePermissionPresentationInfo * * @hide + * + * @deprecated use {@link android.permission.RuntimePermissionPresenterService} instead */ +@Deprecated @SystemApi public abstract class RuntimePermissionPresenterService extends Service { @@ -56,6 +57,9 @@ public abstract class RuntimePermissionPresenterService extends Service { public static final String SERVICE_INTERFACE = "android.permissionpresenterservice.RuntimePermissionPresenterService"; + private static final String KEY_RESULT = + "android.content.pm.permission.RuntimePermissionPresenter.key.result"; + // No need for locking - always set first and never modified private Handler mHandler; @@ -112,7 +116,7 @@ public abstract class RuntimePermissionPresenterService extends Service { List<RuntimePermissionPresentationInfo> permissions = onGetAppPermissions(packageName); if (permissions != null && !permissions.isEmpty()) { Bundle result = new Bundle(); - result.putParcelableList(RuntimePermissionPresenter.KEY_RESULT, permissions); + result.putParcelableList(KEY_RESULT, permissions); callback.sendResult(result); } else { callback.sendResult(null); diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java index a8f3665072ae..c167ea18f0c5 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -16,13 +16,15 @@ package android.provider; - +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.UnsupportedAppUsage; import android.app.Activity; import android.app.AlarmManager; import android.app.PendingIntent; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentUris; @@ -40,6 +42,8 @@ import android.text.format.DateUtils; import android.text.format.Time; import android.util.Log; +import com.android.internal.util.Preconditions; + /** * <p> * The contract between the calendar provider and applications. Contains @@ -128,6 +132,13 @@ public final class CalendarContract { "android.provider.calendar.action.HANDLE_CUSTOM_EVENT"; /** + * Action used to help apps show calendar events in the managed profile. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_VIEW_WORK_CALENDAR_EVENT = + "android.provider.calendar.action.VIEW_WORK_CALENDAR_EVENT"; + + /** * Intent Extras key: {@link EventsColumns#CUSTOM_APP_URI} for the event in * the {@link #ACTION_HANDLE_CUSTOM_EVENT} intent */ @@ -152,6 +163,11 @@ public final class CalendarContract { public static final String EXTRA_EVENT_ALL_DAY = "allDay"; /** + * Intent Extras key: The id of an event. + */ + public static final String EXTRA_EVENT_ID = "id"; + + /** * This authority is used for writing to or querying from the calendar * provider. Note: This is set at first run and cannot be changed without * breaking apps that access the provider. @@ -194,6 +210,43 @@ public final class CalendarContract { private CalendarContract() {} /** + * Starts an activity to view calendar events in the managed profile. + * + * When this API is called, the system will attempt to start an activity + * in the managed profile with an intent targeting the same caller package. + * The intent will have its action set to + * {@link CalendarContract#ACTION_VIEW_WORK_CALENDAR_EVENT} and contain extras + * corresponding to the API's arguments. A calendar app intending to support + * cross profile events viewing should handle this intent, parse the arguments + * and show the appropriate UI. + * + * @param context the context. + * @param eventId the id of the event to be viewed. Will be put into {@link #EXTRA_EVENT_ID} + * field of the intent. + * @param start the start time of the event. Will be put into {@link #EXTRA_EVENT_BEGIN_TIME} + * field of the intent. + * @param end the end time of the event. Will be put into {@link #EXTRA_EVENT_END_TIME} field + * of the intent. + * @param allDay if the event is an all-day event. Will be put into + * {@link #EXTRA_EVENT_ALL_DAY} field of the intent. + * @param flags flags to be set on the intent via {@link Intent#setFlags} + * @return {@code true} if the activity is started successfully. {@code false} otherwise. + * + * @see #EXTRA_EVENT_ID + * @see #EXTRA_EVENT_BEGIN_TIME + * @see #EXTRA_EVENT_END_TIME + * @see #EXTRA_EVENT_ALL_DAY + */ + public static boolean startViewCalendarEventInManagedProfile(@NonNull Context context, + long eventId, long start, long end, boolean allDay, int flags) { + Preconditions.checkNotNull(context, "Context is null"); + final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( + Context.DEVICE_POLICY_SERVICE); + return dpm.startViewCalendarEventInManagedProfile(eventId, start, + end, allDay, flags); + } + + /** * Generic columns for use by sync adapters. The specific functions of these * columns are private to the sync adapter. Other clients of the API should * not attempt to either read or write this column. These columns are @@ -694,6 +747,37 @@ public final class CalendarContract { public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/calendars"); /** + * The content:// style URL for querying Calendars table in the managed profile. Appending a + * calendar id using {@link ContentUris#withAppendedId(Uri, long)} will + * specify a single calendar. + * + * <p>The following columns are allowed to be queried via this uri: + * <ul> + * <li>{@link #_ID}</li> + * <li>{@link #NAME}</li> + * <li>{@link #CALENDAR_DISPLAY_NAME}</li> + * <li>{@link #CALENDAR_COLOR}</li> + * <li>{@link #VISIBLE}</li> + * <li>{@link #CALENDAR_LOCATION}</li> + * <li>{@link #CALENDAR_TIME_ZONE}</li> + * <li>{@link #IS_PRIMARY}</li> + * </ul> + * + * <p>{@link IllegalArgumentException} will be thrown if there exist columns in the + * projection of the query to this uri that are not contained in the above list. + * + * <p>This uri will return an empty cursor if the calling user is not a parent profile + * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is + * queried from a package that is not whitelisted by profile owner of the managed profile + * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. + * + * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName) + * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED + */ + public static final Uri ENTERPRISE_CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/enterprise/calendars"); + + /** * The default sort order for this table */ public static final String DEFAULT_SORT_ORDER = CALENDAR_DISPLAY_NAME; @@ -1641,6 +1725,50 @@ public final class CalendarContract { Uri.parse("content://" + AUTHORITY + "/events"); /** + * The content:// style URL for querying Events table in the managed profile. Appending an + * event id using {@link ContentUris#withAppendedId(Uri, long)} will + * specify a single event. + * + * <p>The following columns are allowed to be queried via this uri: + * <ul> + * <li>{@link #_ID}</li> + * <li>{@link #CALENDAR_ID}</li> + * <li>{@link #TITLE}</li> + * <li>{@link #EVENT_LOCATION}</li> + * <li>{@link #EVENT_COLOR}</li> + * <li>{@link #STATUS}</li> + * <li>{@link #DTSTART}</li> + * <li>{@link #DTEND}</li> + * <li>{@link #EVENT_TIMEZONE}</li> + * <li>{@link #EVENT_END_TIMEZONE}</li> + * <li>{@link #DURATION}</li> + * <li>{@link #ALL_DAY}</li> + * <li>{@link #AVAILABILITY}</li> + * <li>{@link #RRULE}</li> + * <li>{@link #RDATE}</li> + * <li>{@link #EXRULE}</li> + * <li>{@link #EXDATE}</li> + * <li>{@link #CALENDAR_DISPLAY_NAME}</li> + * <li>{@link #CALENDAR_COLOR}</li> + * <li>{@link #VISIBLE}</li> + * <li>{@link #CALENDAR_TIME_ZONE}</li> + * </ul> + * + * <p>{@link IllegalArgumentException} will be thrown if there exist columns in the + * projection of the query to this uri that are not contained in the above list. + * + * <p>This uri will return an empty cursor if the calling user is not a parent profile + * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is + * queried from a package that is not whitelisted by profile owner of the managed profile + * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. + * + * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName) + * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED + */ + public static final Uri ENTERPRISE_CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/enterprise/events"); + + /** * The content:// style URI for recurring event exceptions. Insertions require an * appended event ID. Deletion of exceptions requires both the original event ID and * the exception event ID (see {@link Uri.Builder#appendPath}). @@ -1820,6 +1948,63 @@ public final class CalendarContract { Uri.parse("content://" + AUTHORITY + "/instances/searchbyday"); /** + * The content:// style URL for querying an instance range in the managed profile. + * It supports similar semantics as {@link #CONTENT_URI}. + * + * <p>The following columns plus the columns that are whitelisted by + * {@link Events#ENTERPRISE_CONTENT_URI} are allowed to be queried via this uri: + * <ul> + * <li>{@link #_ID}</li> + * <li>{@link #EVENT_ID}</li> + * <li>{@link #BEGIN}</li> + * <li>{@link #END}</li> + * <li>{@link #START_DAY}</li> + * <li>{@link #END_DAY}</li> + * <li>{@link #START_MINUTE}</li> + * <li>{@link #END_MINUTE}</li> + * </ul> + * + * <p>{@link IllegalArgumentException} will be thrown if there exist columns in the + * projection of the query to this uri that are not contained in the above list. + * + * <p>This uri will return an empty cursor if the calling user is not a parent profile + * of a managed profile, or cross profile calendar for the managed profile is disabled in + * Settings, or this uri is queried from a package that is not whitelisted by + * profile owner of the managed profile via + * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}. + * + * @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName) + * @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED + */ + public static final Uri ENTERPRISE_CONTENT_URI = + Uri.parse("content://" + AUTHORITY + "/enterprise/instances/when"); + + /** + * The content:// style URL for querying an instance range by Julian + * Day in the managed profile. It supports similar semantics as {@link #CONTENT_BY_DAY_URI} + * and performs similar checks as {@link #ENTERPRISE_CONTENT_URI}. + */ + public static final Uri ENTERPRISE_CONTENT_BY_DAY_URI = + Uri.parse("content://" + AUTHORITY + "/enterprise/instances/whenbyday"); + + /** + * The content:// style URL for querying an instance range with a search + * term in the managed profile. It supports similar semantics as {@link #CONTENT_SEARCH_URI} + * and performs similar checks as {@link #ENTERPRISE_CONTENT_URI}. + */ + public static final Uri ENTERPRISE_CONTENT_SEARCH_URI = + Uri.parse("content://" + AUTHORITY + "/enterprise/instances/search"); + + /** + * The content:// style URL for querying an instance range with a search + * term in the managed profile. It supports similar semantics as + * {@link #CONTENT_SEARCH_BY_DAY_URI} and performs similar checks as + * {@link #ENTERPRISE_CONTENT_URI}. + */ + public static final Uri ENTERPRISE_CONTENT_SEARCH_BY_DAY_URI = + Uri.parse("content://" + AUTHORITY + "/enterprise/instances/searchbyday"); + + /** * The default sort order for this table. */ private static final String DEFAULT_SORT_ORDER = "begin ASC"; diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 37c84bd74395..a8726e9de3d0 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -22,8 +22,9 @@ import static com.android.internal.util.Preconditions.checkCollectionNotEmpty; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; -import android.content.ContentProviderClient; +import android.content.ContentInterface; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -47,10 +48,10 @@ import android.os.ParcelFileDescriptor.OnCloseListener; import android.os.Parcelable; import android.os.ParcelableException; import android.os.RemoteException; -import android.os.storage.StorageVolume; -import android.util.DataUnit; import android.util.Log; +import dalvik.system.VMRuntime; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -67,8 +68,7 @@ import java.util.Objects; * All client apps must hold a valid URI permission grant to access documents, * typically issued when a user makes a selection through * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT}, - * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or - * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}. + * or {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. * * @see DocumentsProvider */ @@ -97,8 +97,15 @@ public final class DocumentsContract { @Deprecated public static final String EXTRA_PACKAGE_NAME = Intent.EXTRA_PACKAGE_NAME; - /** {@hide} */ - public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED"; + /** + * The value is decide whether to show advance mode or not. + * If the value is true, the local/device storage root must be + * visible in DocumentsUI. + * + * {@hide} + */ + @SystemApi + public static final String EXTRA_SHOW_ADVANCED = "android.provider.extra.SHOW_ADVANCED"; /** {@hide} */ public static final String EXTRA_TARGET_URI = "android.content.extra.TARGET_URI"; @@ -111,7 +118,6 @@ public final class DocumentsContract { * * @see DocumentsProvider#querySearchDocuments(String, String[], * Bundle) - * {@hide} */ public static final String QUERY_ARG_DISPLAY_NAME = "android:query-arg-display-name"; @@ -124,7 +130,6 @@ public final class DocumentsContract { * * @see DocumentsProvider#querySearchDocuments(String, String[], * Bundle) - * {@hide} */ public static final String QUERY_ARG_MIME_TYPES = "android:query-arg-mime-types"; @@ -134,7 +139,6 @@ public final class DocumentsContract { * * @see DocumentsProvider#querySearchDocuments(String, String[], * Bundle) - * {@hide} */ public static final String QUERY_ARG_FILE_SIZE_OVER = "android:query-arg-file-size-over"; @@ -146,7 +150,6 @@ public final class DocumentsContract { * @see DocumentsProvider#querySearchDocuments(String, String[], * Bundle) * @see Document#COLUMN_LAST_MODIFIED - * {@hide} */ public static final String QUERY_ARG_LAST_MODIFIED_AFTER = "android:query-arg-last-modified-after"; @@ -158,7 +161,6 @@ public final class DocumentsContract { * * @see DocumentsProvider#querySearchDocuments(String, String[], * Bundle) - * {@hide} */ public static final String QUERY_ARG_EXCLUDE_MEDIA = "android:query-arg-exclude-media"; @@ -216,17 +218,20 @@ public final class DocumentsContract { public static final String ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS"; - /** {@hide} */ + /** + * The action to manage document in Downloads root in DocumentsUI. + * {@hide} + */ + @SystemApi public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT"; - /** {@hide} */ - public static final String - ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS"; - /** - * Buffer is large enough to rewind past any EXIF headers. + * The action to launch the settings of this root. + * {@hide} */ - private static final int THUMBNAIL_BUFFER_SIZE = (int) DataUnit.KIBIBYTES.toBytes(128); + @SystemApi + public static final String + ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS"; /** {@hide} */ public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = @@ -235,13 +240,38 @@ public final class DocumentsContract { /** {@hide} */ public static final String PACKAGE_DOCUMENTS_UI = "com.android.documentsui"; - /** {@hide} */ - public static final String METADATA_TYPES = "android:documentMetadataType"; + /** + * Get string array identifies the type or types of metadata returned + * using DocumentsContract#getDocumentMetadata. + * + * @see #getDocumentMetadata(ContentInterface, Uri) + */ + public static final String METADATA_TYPES = "android:documentMetadataTypes"; - /** {@hide} */ + /** + * Get Exif information using DocumentsContract#getDocumentMetadata. + * + * @see #getDocumentMetadata(ContentInterface, Uri) + */ public static final String METADATA_EXIF = "android:documentExif"; /** + * Get total count of all documents currently stored under the given + * directory tree. Only valid for {@link Document#MIME_TYPE_DIR} documents. + * + * @see #getDocumentMetadata(ContentInterface, Uri) + */ + public static final String METADATA_TREE_COUNT = "android:metadataTreeCount"; + + /** + * Get total size of all documents currently stored under the given + * directory tree. Only valid for {@link Document#MIME_TYPE_DIR} documents. + * + * @see #getDocumentMetadata(ContentInterface, Uri) + */ + public static final String METADATA_TREE_SIZE = "android:metadataTreeSize"; + + /** * Constants related to a document, including {@link Cursor} column names * and flags. * <p> @@ -361,7 +391,7 @@ public final class DocumentsContract { * Flag indicating that a document can be represented as a thumbnail. * * @see #COLUMN_FLAGS - * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri, + * @see DocumentsContract#getDocumentThumbnail(ContentInterface, Uri, * Point, CancellationSignal) * @see DocumentsProvider#openDocumentThumbnail(String, Point, * android.os.CancellationSignal) @@ -387,7 +417,7 @@ public final class DocumentsContract { * Flag indicating that a document is deletable. * * @see #COLUMN_FLAGS - * @see DocumentsContract#deleteDocument(ContentResolver, Uri) + * @see DocumentsContract#deleteDocument(ContentInterface, Uri) * @see DocumentsProvider#deleteDocument(String) */ public static final int FLAG_SUPPORTS_DELETE = 1 << 2; @@ -425,8 +455,7 @@ public final class DocumentsContract { * Flag indicating that a document can be renamed. * * @see #COLUMN_FLAGS - * @see DocumentsContract#renameDocument(ContentResolver, Uri, - * String) + * @see DocumentsContract#renameDocument(ContentInterface, Uri, String) * @see DocumentsProvider#renameDocument(String, String) */ public static final int FLAG_SUPPORTS_RENAME = 1 << 6; @@ -436,7 +465,7 @@ public final class DocumentsContract { * within the same document provider. * * @see #COLUMN_FLAGS - * @see DocumentsContract#copyDocument(ContentResolver, Uri, Uri) + * @see DocumentsContract#copyDocument(ContentInterface, Uri, Uri) * @see DocumentsProvider#copyDocument(String, String) */ public static final int FLAG_SUPPORTS_COPY = 1 << 7; @@ -446,7 +475,7 @@ public final class DocumentsContract { * within the same document provider. * * @see #COLUMN_FLAGS - * @see DocumentsContract#moveDocument(ContentResolver, Uri, Uri, Uri) + * @see DocumentsContract#moveDocument(ContentInterface, Uri, Uri, Uri) * @see DocumentsProvider#moveDocument(String, String, String) */ public static final int FLAG_SUPPORTS_MOVE = 1 << 8; @@ -470,7 +499,7 @@ public final class DocumentsContract { * Flag indicating that a document can be removed from a parent. * * @see #COLUMN_FLAGS - * @see DocumentsContract#removeDocument(ContentResolver, Uri, Uri) + * @see DocumentsContract#removeDocument(ContentInterface, Uri, Uri) * @see DocumentsProvider#removeDocument(String, String) */ public static final int FLAG_SUPPORTS_REMOVE = 1 << 10; @@ -498,16 +527,17 @@ public final class DocumentsContract { * if they represent a failed download. * * @see #COLUMN_FLAGS - * @hide */ - public static final int FLAG_PARTIAL = 1 << 16; + public static final int FLAG_PARTIAL = 1 << 13; /** * Flag indicating that a document has available metadata that can be read * using DocumentsContract#getDocumentMetadata - * @hide + * + * @see #COLUMN_FLAGS + * @see DocumentsContract#getDocumentMetadata(ContentInterface, Uri) */ - public static final int FLAG_SUPPORTS_METADATA = 1 << 17; + public static final int FLAG_SUPPORTS_METADATA = 1 << 14; } /** @@ -663,7 +693,7 @@ public final class DocumentsContract { * Flag indicating that this root can be ejected. * * @see #COLUMN_FLAGS - * @see DocumentsContract#ejectRoot(ContentResolver, Uri) + * @see DocumentsContract#ejectRoot(ContentInterface, Uri) * @see DocumentsProvider#ejectRoot(String) */ public static final int FLAG_SUPPORTS_EJECT = 1 << 5; @@ -679,44 +709,46 @@ public final class DocumentsContract { * @see #COLUMN_FLAGS * @see ContentResolver#notifyChange(Uri, * android.database.ContentObserver, boolean) - * @hide */ - public static final int FLAG_EMPTY = 1 << 16; + public static final int FLAG_EMPTY = 1 << 6; /** * Flag indicating that this root should only be visible to advanced * users. * * @see #COLUMN_FLAGS - * @hide + * {@hide} */ - @UnsupportedAppUsage - public static final int FLAG_ADVANCED = 1 << 17; + @SystemApi + public static final int FLAG_ADVANCED = 1 << 16; /** * Flag indicating that this root has settings. * * @see #COLUMN_FLAGS * @see DocumentsContract#ACTION_DOCUMENT_ROOT_SETTINGS - * @hide + * {@hide} */ - public static final int FLAG_HAS_SETTINGS = 1 << 18; + @SystemApi + public static final int FLAG_HAS_SETTINGS = 1 << 17; /** * Flag indicating that this root is on removable SD card storage. * * @see #COLUMN_FLAGS - * @hide + * {@hide} */ - public static final int FLAG_REMOVABLE_SD = 1 << 19; + @SystemApi + public static final int FLAG_REMOVABLE_SD = 1 << 18; /** * Flag indicating that this root is on removable USB storage. * * @see #COLUMN_FLAGS - * @hide + * {@hide} */ - public static final int FLAG_REMOVABLE_USB = 1 << 20; + @SystemApi + public static final int FLAG_REMOVABLE_USB = 1 << 19; } /** @@ -784,10 +816,7 @@ public final class DocumentsContract { /** {@hide} */ public static final String EXTRA_URI_PERMISSIONS = "uriPermissions"; - /** - * @see #createWebLinkIntent(ContentResolver, Uri, Bundle) - * {@hide} - */ + /** {@hide} */ public static final String EXTRA_OPTIONS = "options"; private static final String PATH_ROOT = "root"; @@ -1090,8 +1119,6 @@ public final class DocumentsContract { * Test if the given URI represents roots backed by {@link DocumentsProvider}. * * @see #buildRootsUri(String) - * - * {@hide} */ public static boolean isRootsUri(Context context, @Nullable Uri uri) { return isRootUri(context, uri, 1 /* pathSize */); @@ -1101,8 +1128,6 @@ public final class DocumentsContract { * Test if the given URI represents specific root backed by {@link DocumentsProvider}. * * @see #buildRootUri(String, String) - * - * {@hide} */ public static boolean isRootUri(Context context, @Nullable Uri uri) { return isRootUri(context, uri, 2 /* pathSize */); @@ -1200,13 +1225,23 @@ public final class DocumentsContract { return bundle.getString(QUERY_ARG_DISPLAY_NAME, "" /* defaultValue */); } - /** {@hide} */ - @UnsupportedAppUsage + /** + * Build URI that append the query parameter {@link PARAM_MANAGE} to + * enable the manage mode. + * @see DocumentsProvider#queryChildDocumentsForManage(String parentDocId, String[], String) + * {@hide} + */ + @SystemApi public static Uri setManageMode(Uri uri) { return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build(); } - /** {@hide} */ + /** + * Extract the manage mode from a URI built by + * {@link #setManageMode(Uri)}. + * {@hide} + */ + @SystemApi public static boolean isManageMode(Uri uri) { return uri.getBooleanQueryParameter(PARAM_MANAGE, false); } @@ -1226,30 +1261,24 @@ public final class DocumentsContract { * @see DocumentsProvider#openDocumentThumbnail(String, Point, * android.os.CancellationSignal) */ - public static Bitmap getDocumentThumbnail( - ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) - throws FileNotFoundException { - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - documentUri.getAuthority()); + public static Bitmap getDocumentThumbnail(ContentInterface content, Uri documentUri, Point size, + CancellationSignal signal) throws FileNotFoundException { try { - return getDocumentThumbnail(client, documentUri, size, signal); + return ContentResolver.loadThumbnail(content, documentUri, Point.convert(size), signal, + ImageDecoder.ALLOCATOR_SOFTWARE); } catch (Exception e) { if (!(e instanceof OperationCanceledException)) { Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); } - rethrowIfNecessary(resolver, e); + rethrowIfNecessary(e); return null; - } finally { - ContentProviderClient.releaseQuietly(client); } } - /** {@hide} */ - @UnsupportedAppUsage - public static Bitmap getDocumentThumbnail(ContentProviderClient client, Uri documentUri, - Point size, CancellationSignal signal) throws IOException { - return ContentResolver.loadThumbnail(client, documentUri, Point.convert(size), signal, - ImageDecoder.ALLOCATOR_SOFTWARE); + /** @removed */ + public static Bitmap getDocumentThumbnail(ContentResolver content, Uri documentUri, Point size, + CancellationSignal signal) throws FileNotFoundException { + return getDocumentThumbnail((ContentInterface) content, documentUri, size, signal); } /** @@ -1260,49 +1289,66 @@ public final class DocumentsContract { * @param displayName name of new document * @return newly created document, or {@code null} if failed */ - public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, + public static Uri createDocument(ContentInterface content, Uri parentDocumentUri, String mimeType, String displayName) throws FileNotFoundException { - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - parentDocumentUri.getAuthority()); try { - return createDocument(client, parentDocumentUri, mimeType, displayName); + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri); + in.putString(Document.COLUMN_MIME_TYPE, mimeType); + in.putString(Document.COLUMN_DISPLAY_NAME, displayName); + + final Bundle out = content.call(parentDocumentUri.getAuthority(), + METHOD_CREATE_DOCUMENT, null, in); + return out.getParcelable(DocumentsContract.EXTRA_URI); } catch (Exception e) { Log.w(TAG, "Failed to create document", e); - rethrowIfNecessary(resolver, e); + rethrowIfNecessary(e); return null; - } finally { - ContentProviderClient.releaseQuietly(client); } } - /** {@hide} */ - public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri, - String mimeType, String displayName) throws RemoteException { - final Bundle in = new Bundle(); - in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri); - in.putString(Document.COLUMN_MIME_TYPE, mimeType); - in.putString(Document.COLUMN_DISPLAY_NAME, displayName); - - final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in); - return out.getParcelable(DocumentsContract.EXTRA_URI); + /** @removed */ + public static Uri createDocument(ContentResolver content, Uri parentDocumentUri, + String mimeType, String displayName) throws FileNotFoundException { + return createDocument((ContentInterface) content, parentDocumentUri, mimeType, displayName); } - /** {@hide} */ - public static boolean isChildDocument(ContentProviderClient client, Uri parentDocumentUri, - Uri childDocumentUri) throws RemoteException { - - final Bundle in = new Bundle(); - in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri); - in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri); - - final Bundle out = client.call(METHOD_IS_CHILD_DOCUMENT, null, in); - if (out == null) { - throw new RemoteException("Failed to get a reponse from isChildDocument query."); - } - if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) { - throw new RemoteException("Response did not include result field.."); + /** + * Test if a document is descendant (child, grandchild, etc) from the given + * parent. + * + * @param parentDocumentUri parent to verify against. + * @param childDocumentUri child to verify. + * @return if given document is a descendant of the given parent. + * @see Root#FLAG_SUPPORTS_IS_CHILD + */ + public static boolean isChildDocument(ContentInterface content, Uri parentDocumentUri, + Uri childDocumentUri) throws FileNotFoundException { + try { + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri); + in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri); + + final Bundle out = content.call(parentDocumentUri.getAuthority(), + METHOD_IS_CHILD_DOCUMENT, null, in); + if (out == null) { + throw new RemoteException("Failed to get a reponse from isChildDocument query."); + } + if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) { + throw new RemoteException("Response did not include result field.."); + } + return out.getBoolean(DocumentsContract.EXTRA_RESULT); + } catch (Exception e) { + Log.w(TAG, "Failed to create document", e); + rethrowIfNecessary(e); + return false; } - return out.getBoolean(DocumentsContract.EXTRA_RESULT); + } + + /** @removed */ + public static boolean isChildDocument(ContentResolver content, Uri parentDocumentUri, + Uri childDocumentUri) throws FileNotFoundException { + return isChildDocument((ContentInterface) content, parentDocumentUri, childDocumentUri); } /** @@ -1318,31 +1364,28 @@ public final class DocumentsContract { * @return the existing or new document after the rename, or {@code null} if * failed. */ - public static Uri renameDocument(ContentResolver resolver, Uri documentUri, + public static Uri renameDocument(ContentInterface content, Uri documentUri, String displayName) throws FileNotFoundException { - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - documentUri.getAuthority()); try { - return renameDocument(client, documentUri, displayName); + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); + in.putString(Document.COLUMN_DISPLAY_NAME, displayName); + + final Bundle out = content.call(documentUri.getAuthority(), + METHOD_RENAME_DOCUMENT, null, in); + final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI); + return (outUri != null) ? outUri : documentUri; } catch (Exception e) { Log.w(TAG, "Failed to rename document", e); - rethrowIfNecessary(resolver, e); + rethrowIfNecessary(e); return null; - } finally { - ContentProviderClient.releaseQuietly(client); } } - /** {@hide} */ - public static Uri renameDocument(ContentProviderClient client, Uri documentUri, - String displayName) throws RemoteException { - final Bundle in = new Bundle(); - in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); - in.putString(Document.COLUMN_DISPLAY_NAME, displayName); - - final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in); - final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI); - return (outUri != null) ? outUri : documentUri; + /** @removed */ + public static Uri renameDocument(ContentResolver content, Uri documentUri, + String displayName) throws FileNotFoundException { + return renameDocument((ContentInterface) content, documentUri, displayName); } /** @@ -1351,29 +1394,26 @@ public final class DocumentsContract { * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} * @return if the document was deleted successfully. */ - public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) + public static boolean deleteDocument(ContentInterface content, Uri documentUri) throws FileNotFoundException { - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - documentUri.getAuthority()); try { - deleteDocument(client, documentUri); + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); + + content.call(documentUri.getAuthority(), + METHOD_DELETE_DOCUMENT, null, in); return true; } catch (Exception e) { Log.w(TAG, "Failed to delete document", e); - rethrowIfNecessary(resolver, e); + rethrowIfNecessary(e); return false; - } finally { - ContentProviderClient.releaseQuietly(client); } } - /** {@hide} */ - public static void deleteDocument(ContentProviderClient client, Uri documentUri) - throws RemoteException { - final Bundle in = new Bundle(); - in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); - - client.call(METHOD_DELETE_DOCUMENT, null, in); + /** @removed */ + public static boolean deleteDocument(ContentResolver content, Uri documentUri) + throws FileNotFoundException { + return deleteDocument((ContentInterface) content, documentUri); } /** @@ -1384,30 +1424,27 @@ public final class DocumentsContract { * document's copy. * @return the copied document, or {@code null} if failed. */ - public static Uri copyDocument(ContentResolver resolver, Uri sourceDocumentUri, + public static Uri copyDocument(ContentInterface content, Uri sourceDocumentUri, Uri targetParentDocumentUri) throws FileNotFoundException { - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - sourceDocumentUri.getAuthority()); try { - return copyDocument(client, sourceDocumentUri, targetParentDocumentUri); + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri); + in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri); + + final Bundle out = content.call(sourceDocumentUri.getAuthority(), + METHOD_COPY_DOCUMENT, null, in); + return out.getParcelable(DocumentsContract.EXTRA_URI); } catch (Exception e) { Log.w(TAG, "Failed to copy document", e); - rethrowIfNecessary(resolver, e); + rethrowIfNecessary(e); return null; - } finally { - ContentProviderClient.releaseQuietly(client); } } - /** {@hide} */ - public static Uri copyDocument(ContentProviderClient client, Uri sourceDocumentUri, - Uri targetParentDocumentUri) throws RemoteException { - final Bundle in = new Bundle(); - in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri); - in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri); - - final Bundle out = client.call(METHOD_COPY_DOCUMENT, null, in); - return out.getParcelable(DocumentsContract.EXTRA_URI); + /** @removed */ + public static Uri copyDocument(ContentResolver content, Uri sourceDocumentUri, + Uri targetParentDocumentUri) throws FileNotFoundException { + return copyDocument((ContentInterface) content, sourceDocumentUri, targetParentDocumentUri); } /** @@ -1419,33 +1456,29 @@ public final class DocumentsContract { * document. * @return the moved document, or {@code null} if failed. */ - public static Uri moveDocument(ContentResolver resolver, Uri sourceDocumentUri, + public static Uri moveDocument(ContentInterface content, Uri sourceDocumentUri, Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws FileNotFoundException { - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - sourceDocumentUri.getAuthority()); try { - return moveDocument(client, sourceDocumentUri, sourceParentDocumentUri, - targetParentDocumentUri); + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri); + in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, sourceParentDocumentUri); + in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri); + + final Bundle out = content.call(sourceDocumentUri.getAuthority(), + METHOD_MOVE_DOCUMENT, null, in); + return out.getParcelable(DocumentsContract.EXTRA_URI); } catch (Exception e) { Log.w(TAG, "Failed to move document", e); - rethrowIfNecessary(resolver, e); + rethrowIfNecessary(e); return null; - } finally { - ContentProviderClient.releaseQuietly(client); } } - /** {@hide} */ - @UnsupportedAppUsage - public static Uri moveDocument(ContentProviderClient client, Uri sourceDocumentUri, - Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws RemoteException { - final Bundle in = new Bundle(); - in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri); - in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, sourceParentDocumentUri); - in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri); - - final Bundle out = client.call(METHOD_MOVE_DOCUMENT, null, in); - return out.getParcelable(DocumentsContract.EXTRA_URI); + /** @removed */ + public static Uri moveDocument(ContentResolver content, Uri sourceDocumentUri, + Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws FileNotFoundException { + return moveDocument((ContentInterface) content, sourceDocumentUri, sourceParentDocumentUri, + targetParentDocumentUri); } /** @@ -1458,30 +1491,27 @@ public final class DocumentsContract { * @param parentDocumentUri parent document of the document to remove. * @return true if the document was removed successfully. */ - public static boolean removeDocument(ContentResolver resolver, Uri documentUri, + public static boolean removeDocument(ContentInterface content, Uri documentUri, Uri parentDocumentUri) throws FileNotFoundException { - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - documentUri.getAuthority()); try { - removeDocument(client, documentUri, parentDocumentUri); + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); + in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, parentDocumentUri); + + content.call(documentUri.getAuthority(), + METHOD_REMOVE_DOCUMENT, null, in); return true; } catch (Exception e) { Log.w(TAG, "Failed to remove document", e); - rethrowIfNecessary(resolver, e); + rethrowIfNecessary(e); return false; - } finally { - ContentProviderClient.releaseQuietly(client); } } - /** {@hide} */ - public static void removeDocument(ContentProviderClient client, Uri documentUri, - Uri parentDocumentUri) throws RemoteException { - final Bundle in = new Bundle(); - in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); - in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, parentDocumentUri); - - client.call(METHOD_REMOVE_DOCUMENT, null, in); + /** @removed */ + public static boolean removeDocument(ContentResolver content, Uri documentUri, + Uri parentDocumentUri) throws FileNotFoundException { + return removeDocument((ContentInterface) content, documentUri, parentDocumentUri); } /** @@ -1489,37 +1519,33 @@ public final class DocumentsContract { * * @param rootUri root with {@link Root#FLAG_SUPPORTS_EJECT} to be ejected */ - public static void ejectRoot(ContentResolver resolver, Uri rootUri) { - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - rootUri.getAuthority()); + public static void ejectRoot(ContentInterface content, Uri rootUri) { try { - ejectRoot(client, rootUri); + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, rootUri); + + content.call(rootUri.getAuthority(), + METHOD_EJECT_ROOT, null, in); } catch (RemoteException e) { e.rethrowAsRuntimeException(); - } finally { - ContentProviderClient.releaseQuietly(client); } } - /** {@hide} */ - public static void ejectRoot(ContentProviderClient client, Uri rootUri) - throws RemoteException { - final Bundle in = new Bundle(); - in.putParcelable(DocumentsContract.EXTRA_URI, rootUri); - - client.call(METHOD_EJECT_ROOT, null, in); + /** @removed */ + public static void ejectRoot(ContentResolver content, Uri rootUri) { + ejectRoot((ContentInterface) content, rootUri); } /** * Returns metadata associated with the document. The type of metadata returned * is specific to the document type. For example the data returned for an image - * file will likely consist primarily or soley of EXIF metadata. + * file will likely consist primarily or solely of EXIF metadata. * * <p>The returned {@link Bundle} will contain zero or more entries depending * on the type of data supported by the document provider. * * <ol> - * <li>A {@link DocumentsContract.METADATA_TYPES} containing a {@code String[]} value. + * <li>A {@link DocumentsContract#METADATA_TYPES} containing a {@code String[]} value. * The string array identifies the type or types of metadata returned. Each * value in the can be used to access a {@link Bundle} of data * containing that type of data. @@ -1539,66 +1565,26 @@ public final class DocumentsContract { * * @param documentUri a Document URI * @return a Bundle of Bundles. - * {@hide} */ - public static Bundle getDocumentMetadata(ContentResolver resolver, Uri documentUri) + public static Bundle getDocumentMetadata(ContentInterface content, Uri documentUri) throws FileNotFoundException { - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - documentUri.getAuthority()); - try { - return getDocumentMetadata(client, documentUri); + final Bundle in = new Bundle(); + in.putParcelable(EXTRA_URI, documentUri); + + return content.call(documentUri.getAuthority(), + METHOD_GET_DOCUMENT_METADATA, null, in); } catch (Exception e) { Log.w(TAG, "Failed to get document metadata"); - rethrowIfNecessary(resolver, e); + rethrowIfNecessary(e); return null; - } finally { - ContentProviderClient.releaseQuietly(client); } } - /** - * Returns metadata associated with the document. The type of metadata returned - * is specific to the document type. For example the data returned for an image - * file will likely consist primarily or soley of EXIF metadata. - * - * <p>The returned {@link Bundle} will contain zero or more entries depending - * on the type of data supported by the document provider. - * - * <ol> - * <li>A {@link DocumentsContract.METADATA_TYPES} containing a {@code String[]} value. - * The string array identifies the type or types of metadata returned. Each - * value in the can be used to access a {@link Bundle} of data - * containing that type of data. - * <li>An entry each for each type of returned metadata. Each set of metadata is - * itself represented as a bundle and accessible via a string key naming - * the type of data. - * </ol> - * - * <p>Example: - * <p><pre><code> - * Bundle metadata = DocumentsContract.getDocumentMetadata(client, imageDocUri, tags); - * if (metadata.containsKey(DocumentsContract.METADATA_EXIF)) { - * Bundle exif = metadata.getBundle(DocumentsContract.METADATA_EXIF); - * int imageLength = exif.getInt(ExifInterface.TAG_IMAGE_LENGTH); - * } - * </code></pre> - * - * @param documentUri a Document URI - * @return a Bundle of Bundles. - * {@hide} - */ - public static Bundle getDocumentMetadata( - ContentProviderClient client, Uri documentUri) throws RemoteException { - final Bundle in = new Bundle(); - in.putParcelable(EXTRA_URI, documentUri); - - final Bundle out = client.call(METHOD_GET_DOCUMENT_METADATA, null, in); - - if (out == null) { - throw new RemoteException("Failed to get a response from getDocumentMetadata"); - } - return out; + /** @removed */ + public static Bundle getDocumentMetadata(ContentResolver content, Uri documentUri) + throws FileNotFoundException { + return getDocumentMetadata((ContentInterface) content, documentUri); } /** @@ -1614,49 +1600,28 @@ public final class DocumentsContract { * @return the path of the document, or {@code null} if failed. * @see DocumentsProvider#findDocumentPath(String, String) */ - public static Path findDocumentPath(ContentResolver resolver, Uri treeUri) + public static Path findDocumentPath(ContentInterface content, Uri treeUri) throws FileNotFoundException { checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri."); - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - treeUri.getAuthority()); try { - return findDocumentPath(client, treeUri); + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, treeUri); + + final Bundle out = content.call(treeUri.getAuthority(), + METHOD_FIND_DOCUMENT_PATH, null, in); + return out.getParcelable(DocumentsContract.EXTRA_RESULT); } catch (Exception e) { Log.w(TAG, "Failed to find path", e); - rethrowIfNecessary(resolver, e); + rethrowIfNecessary(e); return null; - } finally { - ContentProviderClient.releaseQuietly(client); } } - /** - * Finds the canonical path. If uri is a document uri returns path from a root and - * its associated root id. If uri is a tree uri returns the path from the top of - * the tree. The {@link Path#getPath()} of the return value contains document ID - * starts from the top of the tree or the root document to the requested document, - * both inclusive. - * - * Callers can expect the root ID returned from multiple calls to this method is - * consistent. - * - * @param uri uri of the document which path is requested. It can be either a - * plain document uri or a tree uri. - * @return the path of the document. - * @see DocumentsProvider#findDocumentPath(String, String) - * - * {@hide} - */ - public static Path findDocumentPath(ContentProviderClient client, Uri uri) - throws RemoteException { - - final Bundle in = new Bundle(); - in.putParcelable(DocumentsContract.EXTRA_URI, uri); - - final Bundle out = client.call(METHOD_FIND_DOCUMENT_PATH, null, in); - - return out.getParcelable(DocumentsContract.EXTRA_RESULT); + /** @removed */ + public static Path findDocumentPath(ContentResolver content, Uri treeUri) + throws FileNotFoundException { + return findDocumentPath((ContentInterface) content, treeUri); } /** @@ -1710,37 +1675,32 @@ public final class DocumentsContract { * @see DocumentsProvider#createWebLinkIntent(String, Bundle) * @see Intent#EXTRA_EMAIL */ - public static IntentSender createWebLinkIntent(ContentResolver resolver, Uri uri, + public static IntentSender createWebLinkIntent(ContentInterface content, Uri uri, Bundle options) throws FileNotFoundException { - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - uri.getAuthority()); try { - return createWebLinkIntent(client, uri, options); + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, uri); + + // Options may be provider specific, so put them in a separate bundle to + // avoid overriding the Uri. + if (options != null) { + in.putBundle(EXTRA_OPTIONS, options); + } + + final Bundle out = content.call(uri.getAuthority(), + METHOD_CREATE_WEB_LINK_INTENT, null, in); + return out.getParcelable(DocumentsContract.EXTRA_RESULT); } catch (Exception e) { Log.w(TAG, "Failed to create a web link intent", e); - rethrowIfNecessary(resolver, e); + rethrowIfNecessary(e); return null; - } finally { - ContentProviderClient.releaseQuietly(client); } } - /** - * {@hide} - */ - public static IntentSender createWebLinkIntent(ContentProviderClient client, Uri uri, - Bundle options) throws RemoteException { - final Bundle in = new Bundle(); - in.putParcelable(DocumentsContract.EXTRA_URI, uri); - - // Options may be provider specific, so put them in a separate bundle to - // avoid overriding the Uri. - if (options != null) { - in.putBundle(EXTRA_OPTIONS, options); - } - - final Bundle out = client.call(METHOD_CREATE_WEB_LINK_INTENT, null, in); - return out.getParcelable(DocumentsContract.EXTRA_RESULT); + /** @removed */ + public static IntentSender createWebLinkIntent(ContentResolver content, Uri uri, + Bundle options) throws FileNotFoundException { + return createWebLinkIntent((ContentInterface) content, uri, options); } /** @@ -1783,10 +1743,9 @@ public final class DocumentsContract { return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras); } - private static void rethrowIfNecessary(ContentResolver resolver, Exception e) - throws FileNotFoundException { + private static void rethrowIfNecessary(Exception e) throws FileNotFoundException { // We only want to throw applications targetting O and above - if (resolver.getTargetSdkVersion() >= Build.VERSION_CODES.O) { + if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.O) { if (e instanceof ParcelableException) { ((ParcelableException) e).maybeRethrow(FileNotFoundException.class); } else if (e instanceof RemoteException) { diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 6ab72c7bb372..70c84f8cc324 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -691,7 +691,6 @@ public abstract class DocumentsProvider extends ContentProvider { * @see DocumentsContract#EXTRA_LOADING * @see DocumentsContract#EXTRA_INFO * @see DocumentsContract#EXTRA_ERROR - * {@hide} */ @SuppressWarnings("unused") public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs) @@ -711,7 +710,28 @@ public abstract class DocumentsProvider extends ContentProvider { throw new UnsupportedOperationException("Eject not supported"); } - /** {@hide} */ + /** + * Returns metadata associated with the document. The type of metadata returned + * is specific to the document type. For example the data returned for an image + * file will likely consist primarily or solely of EXIF metadata. + * + * <p>The returned {@link Bundle} will contain zero or more entries depending + * on the type of data supported by the document provider. + * + * <ol> + * <li>A {@link DocumentsContract#METADATA_TYPES} containing a {@code String[]} value. + * The string array identifies the type or types of metadata returned. Each + * value in the can be used to access a {@link Bundle} of data + * containing that type of data. + * <li>An entry each for each type of returned metadata. Each set of metadata is + * itself represented as a bundle and accessible via a string key naming + * the type of data. + * </ol> + * + * @param documentId get the metadata of the document + * @return a Bundle of Bundles. + * @see DocumentsContract#getDocumentMetadata(ContentResolver, Uri) + */ public @Nullable Bundle getDocumentMetadata(String documentId) throws FileNotFoundException { throw new UnsupportedOperationException("Metadata not supported"); diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 0299e416707a..f38f74046934 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -16,11 +16,14 @@ package android.provider; +import android.annotation.BytesLong; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.Activity; import android.app.AppGlobals; @@ -46,6 +49,7 @@ import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import android.os.storage.VolumeInfo; @@ -66,6 +70,7 @@ import java.io.OutputStream; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.regex.Pattern; /** * The Media provider contains meta data for all available media on both internal @@ -102,6 +107,11 @@ public final class MediaStore { /** {@hide} */ public static final String GET_MEDIA_URI_CALL = "get_media_uri"; + /** {@hide} */ + public static final String GET_CONTRIBUTED_MEDIA_CALL = "get_contributed_media"; + /** {@hide} */ + public static final String DELETE_CONTRIBUTED_MEDIA_CALL = "delete_contributed_media"; + /** * This is for internal use by the media scanner only. * Name of the (optional) Uri parameter that determines whether to skip deleting @@ -1037,6 +1047,20 @@ public final class MediaStore { getContentUri("external"); /** + * The MIME type for this table. + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/download"; + + /** + * Regex that matches paths that needs to be considered part of downloads collection. + * @hide + */ + public static final Pattern PATTERN_DOWNLOADS_FILE = Pattern.compile( + "(?i)^/storage/[^/]+/(?:[0-9]+/)?(?:Android/sandbox/[^/]+/)?Download/.+"); + private static final Pattern PATTERN_DOWNLOADS_DIRECTORY = Pattern.compile( + "(?i)^/storage/[^/]+/(?:[0-9]+/)?(?:Android/sandbox/[^/]+/)?Download/?"); + + /** * Get the content:// style URI for the downloads table on the * given volume. * @@ -1052,6 +1076,16 @@ public final class MediaStore { public static Uri getContentUriForPath(@NonNull String path) { return getContentUri(getVolumeNameForPath(path)); } + + /** @hide */ + public static boolean isDownload(@NonNull String path) { + return PATTERN_DOWNLOADS_FILE.matcher(path).matches(); + } + + /** @hide */ + public static boolean isDownloadDir(@NonNull String path) { + return PATTERN_DOWNLOADS_DIRECTORY.matcher(path).matches(); + } } private static String getVolumeNameForPath(@NonNull String path) { @@ -2865,4 +2899,59 @@ public final class MediaStore { throw e.rethrowAsRuntimeException(); } } + + /** + * Calculate size of media contributed by given package under the calling + * user. The meaning of "contributed" means it won't automatically be + * deleted when the app is uninstalled. + * + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) + public static @BytesLong long getContributedMediaSize(Context context, String packageName, + UserHandle user) throws IOException { + final UserManager um = context.getSystemService(UserManager.class); + if (um.isUserUnlocked(user) && um.isUserRunning(user)) { + try { + final ContentResolver resolver = context + .createPackageContextAsUser(packageName, 0, user).getContentResolver(); + final Bundle in = new Bundle(); + in.putString(Intent.EXTRA_PACKAGE_NAME, packageName); + final Bundle out = resolver.call(AUTHORITY, GET_CONTRIBUTED_MEDIA_CALL, null, in); + return out.getLong(Intent.EXTRA_INDEX); + } catch (Exception e) { + throw new IOException(e); + } + } else { + throw new IOException("User " + user + " must be unlocked and running"); + } + } + + /** + * Delete all media contributed by given package under the calling user. The + * meaning of "contributed" means it won't automatically be deleted when the + * app is uninstalled. + * + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) + public static void deleteContributedMedia(Context context, String packageName, + UserHandle user) throws IOException { + final UserManager um = context.getSystemService(UserManager.class); + if (um.isUserUnlocked(user) && um.isUserRunning(user)) { + try { + final ContentResolver resolver = context + .createPackageContextAsUser(packageName, 0, user).getContentResolver(); + final Bundle in = new Bundle(); + in.putString(Intent.EXTRA_PACKAGE_NAME, packageName); + resolver.call(AUTHORITY, DELETE_CONTRIBUTED_MEDIA_CALL, null, in); + } catch (Exception e) { + throw new IOException(e); + } + } else { + throw new IOException("User " + user + " must be unlocked and running"); + } + } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c3217a2189cf..93a59502ebea 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1680,6 +1680,11 @@ public final class Settings { */ public static final String CALL_METHOD_TAG_KEY = "_tag"; + /** + * @hide - String argument extra to the fast-path call()-based requests + */ + public static final String CALL_METHOD_PREFIX_KEY = "_prefix"; + /** @hide - Private call() method to write to 'system' table */ public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system"; @@ -1701,15 +1706,18 @@ public final class Settings { /** @hide - Private call() method to delete from the 'global' table */ public static final String CALL_METHOD_DELETE_GLOBAL = "DELETE_global"; + /** @hide - Private call() method to reset to defaults the 'configuration' table */ + public static final String CALL_METHOD_DELETE_CONFIG = "DELETE_config"; + + /** @hide - Private call() method to reset to defaults the 'secure' table */ + public static final String CALL_METHOD_RESET_SECURE = "RESET_secure"; + /** @hide - Private call() method to reset to defaults the 'global' table */ public static final String CALL_METHOD_RESET_GLOBAL = "RESET_global"; /** @hide - Private call() method to reset to defaults the 'configuration' table */ public static final String CALL_METHOD_RESET_CONFIG = "RESET_config"; - /** @hide - Private call() method to reset to defaults the 'secure' table */ - public static final String CALL_METHOD_RESET_SECURE = "RESET_secure"; - /** @hide - Private call() method to query the 'system' table */ public static final String CALL_METHOD_LIST_SYSTEM = "LIST_system"; @@ -1719,6 +1727,9 @@ public final class Settings { /** @hide - Private call() method to query the 'global' table */ public static final String CALL_METHOD_LIST_GLOBAL = "LIST_global"; + /** @hide - Private call() method to reset to defaults the 'configuration' table */ + public static final String CALL_METHOD_LIST_CONFIG = "LIST_config"; + /** * Activity Extra: Limit available options in launched activity based on the given authority. * <p> @@ -2044,7 +2055,8 @@ public final class Settings { arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true); } IContentProvider cp = mProviderHolder.getProvider(cr); - cp.call(cr.getPackageName(), mCallSetCommand, name, arg); + cp.call(cr.getPackageName(), mProviderHolder.mUri.getAuthority(), + mCallSetCommand, name, arg); } catch (RemoteException e) { Log.w(TAG, "Can't set key " + name + " in " + mUri, e); return false; @@ -2117,12 +2129,14 @@ public final class Settings { if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) { final long token = Binder.clearCallingIdentity(); try { - b = cp.call(cr.getPackageName(), mCallGetCommand, name, args); + b = cp.call(cr.getPackageName(), mProviderHolder.mUri.getAuthority(), + mCallGetCommand, name, args); } finally { Binder.restoreCallingIdentity(token); } } else { - b = cp.call(cr.getPackageName(), mCallGetCommand, name, args); + b = cp.call(cr.getPackageName(), mProviderHolder.mUri.getAuthority(), + mCallGetCommand, name, args); } if (b != null) { String value = b.getString(Settings.NameValueTable.VALUE); @@ -5112,7 +5126,8 @@ public final class Settings { } arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode); IContentProvider cp = sProviderHolder.getProvider(resolver); - cp.call(resolver.getPackageName(), CALL_METHOD_RESET_SECURE, null, arg); + cp.call(resolver.getPackageName(), sProviderHolder.mUri.getAuthority(), + CALL_METHOD_RESET_SECURE, null, arg); } catch (RemoteException e) { Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e); } @@ -11863,10 +11878,26 @@ public final class Settings { public static final String GPU_DEBUG_APP = "gpu_debug_app"; /** - * App should try to use ANGLE + * Force all PKGs to use ANGLE, regardless of any other settings + * The value is a boolean (1 or 0). * @hide */ - public static final String ANGLE_ENABLED_APP = "angle_enabled_app"; + public static final String GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE = + "angle_gl_driver_all_angle"; + + /** + * List of PKGs that have an OpenGL driver selected + * @hide + */ + public static final String GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS = + "angle_gl_driver_selection_pkgs"; + + /** + * List of selected OpenGL drivers, corresponding to the PKGs in GLOBAL_SETTINGS_DRIVER_PKGS + * @hide + */ + public static final String GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES = + "angle_gl_driver_selection_values"; /** * App that is selected to use updated graphics driver. @@ -12821,6 +12852,13 @@ public final class Settings { public static final String HIDDEN_API_POLICY = "hidden_api_policy"; /** + * Current version of signed configuration applied. + * + * @hide + */ + public static final String SIGNED_CONFIG_VERSION = "signed_config_version"; + + /** * Timeout for a single {@link android.media.soundtrigger.SoundTriggerDetectionService} * operation (in ms). * @@ -12840,7 +12878,7 @@ public final class Settings { /** * Property used by {@code com.android.server.SystemServer} on start to decide whether - * the Smart Suggestions service should be created or not + * the Content Capture service should be created or not * * <p>By default it should *NOT* be set (in which case the decision is based on whether * the OEM provides an implementation for the service), but it can be overridden to: @@ -12852,8 +12890,8 @@ public final class Settings { * * @hide */ - public static final String SMART_SUGGESTIONS_SERVICE_EXPLICITLY_ENABLED = - "smart_suggestions_service_explicitly_enabled"; + public static final String CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED = + "content_capture_service_explicitly_enabled"; /** * Settings to backup. This is here so that it's in the same place as the settings @@ -13147,7 +13185,8 @@ public final class Settings { } arg.putInt(CALL_METHOD_RESET_MODE_KEY, mode); IContentProvider cp = sProviderHolder.getProvider(resolver); - cp.call(resolver.getPackageName(), CALL_METHOD_RESET_GLOBAL, null, arg); + cp.call(resolver.getPackageName(), sProviderHolder.mUri.getAuthority(), + CALL_METHOD_RESET_GLOBAL, null, arg); } catch (RemoteException e) { Log.w(TAG, "Can't reset do defaults for " + CONTENT_URI, e); } @@ -13671,6 +13710,22 @@ public final class Settings { "smart_replies_in_notifications_flags"; /** + * Configuration flags for the automatic generation of smart replies and smart actions in + * notifications. This is encoded as a key=value list, separated by commas. Ex: + * "generate_replies=false,generate_actions=true". + * + * The following keys are supported: + * + * <pre> + * generate_replies (boolean) + * generate_actions (boolean) + * </pre> + * @hide + */ + public static final String SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS = + "smart_suggestions_in_notifications_flags"; + + /** * If nonzero, crashes in foreground processes will bring up a dialog. * Otherwise, the process will be silently killed. * @hide @@ -13747,6 +13802,14 @@ public final class Settings { "backup_agent_timeout_parameters"; /** + * Whether the backup system service supports multiple users (0 = disabled, 1 = enabled). If + * disabled, the service will only be active for the system user. + * + * @hide + */ + public static final String BACKUP_MULTI_USER_ENABLED = "backup_multi_user_enabled"; + + /** * Blacklist of GNSS satellites. * * This is a list of integers separated by commas to represent pairs of (constellation, @@ -13870,13 +13933,6 @@ public final class Settings { /** * Store a name/value pair into the database. * <p> - * The method takes an optional tag to associate with the setting which can be used to clear - * only settings made by your package and associated with this tag by passing the tag to - * {@link #resetToDefaults(ContentResolver, String)}. The value of this setting can be - * overridden by future calls to this or other put methods, and the tag provided in those - * calls, which may be null, will override the tag provided in this call. Any call to a put - * method which does not accept a tag will effectively set the tag to null. - * </p><p> * Also the method takes an argument whether to make the value the default for this setting. * If the system already specified a default value, then the one passed in here will * <strong>not</strong> be set as the default. @@ -13885,48 +13941,50 @@ public final class Settings { * @param resolver to access the database with. * @param name to store. * @param value to associate with the name. - * @param tag to associated with the setting. * @param makeDefault whether to make the value the default one. * @return true if the value was set, false on database errors. * - * @see #resetToDefaults(ContentResolver, String) + * @see #resetToDefaults(ContentResolver, int, String) * * @hide */ // TODO(b/117663715): require a new write permission restricted to a single source @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) - static boolean putString(@NonNull ContentResolver resolver, - @NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault) { - return sNameValueCache.putStringForUser(resolver, name, value, tag, makeDefault, + static boolean putString(@NonNull ContentResolver resolver, @NonNull String name, + @Nullable String value, boolean makeDefault) { + return sNameValueCache.putStringForUser(resolver, name, value, null, makeDefault, resolver.getUserId()); } /** - * Reset the settings to their defaults. This would reset <strong>only</strong> settings set - * by the caller's package. Passing in the optional tag will reset only settings changed by - * your package and associated with this tag. + * Reset the values to their defaults. + * <p> + * The method accepts an optional prefix parameter. If provided, only pairs with a name that + * starts with the exact prefix will be reset. Otherwise all will be reset. * * @param resolver Handle to the content resolver. - * @param tag Optional tag which should be associated with the settings to reset. + * @param resetMode The reset mode to use. + * @param prefix Optionally, to limit which which pairs are reset. * - * @see #putString(ContentResolver, String, String, String, boolean) + * @see #putString(ContentResolver, String, String, boolean) * * @hide */ // TODO(b/117663715): require a new write permission restricted to a single source @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) - static void resetToDefaults(@NonNull ContentResolver resolver, - @Nullable String tag) { + static void resetToDefaults(@NonNull ContentResolver resolver, @ResetMode int resetMode, + @Nullable String prefix) { try { Bundle arg = new Bundle(); arg.putInt(CALL_METHOD_USER_KEY, resolver.getUserId()); - if (tag != null) { - arg.putString(CALL_METHOD_TAG_KEY, tag); + arg.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, resetMode); + if (prefix != null) { + arg.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix); } - arg.putInt(CALL_METHOD_RESET_MODE_KEY, RESET_MODE_PACKAGE_DEFAULTS); + arg.putInt(CALL_METHOD_RESET_MODE_KEY, resetMode); IContentProvider cp = sProviderHolder.getProvider(resolver); - cp.call(resolver.getPackageName(), CALL_METHOD_RESET_CONFIG, null, arg); + cp.call(resolver.getPackageName(), sProviderHolder.mUri.getAuthority(), + CALL_METHOD_RESET_CONFIG, null, arg); } catch (RemoteException e) { Log.w(TAG, "Can't reset to defaults for " + CONTENT_URI, e); } diff --git a/core/java/android/service/autofill/AutofillFieldClassificationService.java b/core/java/android/service/autofill/AutofillFieldClassificationService.java index 1cd76d2e9ec9..261291741f96 100644 --- a/core/java/android/service/autofill/AutofillFieldClassificationService.java +++ b/core/java/android/service/autofill/AutofillFieldClassificationService.java @@ -20,6 +20,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Service; import android.content.Intent; import android.os.Bundle; @@ -35,6 +36,7 @@ import android.view.autofill.AutofillValue; import java.util.Arrays; import java.util.List; +import java.util.Map; /** * A service that calculates field classification scores. @@ -51,6 +53,7 @@ import java.util.List; * {@hide} */ @SystemApi +@TestApi public abstract class AutofillFieldClassificationService extends Service { private static final String TAG = "AutofillFieldClassificationService"; @@ -75,17 +78,32 @@ public abstract class AutofillFieldClassificationService extends Service { public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS = "android.autofill.field_classification.available_algorithms"; + /** + * Field classification algorithm that computes the edit distance between two Strings. + * + * <p>Service implementation must provide this algorithm.</p> + */ + public static final String REQUIRED_ALGORITHM_EDIT_DISTANCE = "EDIT_DISTANCE"; + + /** + * Field classification algorithm that computes whether the last four digits between two + * Strings match exactly. + * + * <p>Service implementation must provide this algorithm.</p> + */ + public static final String REQUIRED_ALGORITHM_EXACT_MATCH = "EXACT_MATCH"; /** {@hide} **/ public static final String EXTRA_SCORES = "scores"; private AutofillFieldClassificationServiceWrapper mWrapper; - private void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs, - List<AutofillValue> actualValues, String[] userDataValues) { + private void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues, + String[] userDataValues, String[] categoryIds, String defaultAlgorithm, + Bundle defaultArgs, Map algorithms, Map args) { final Bundle data = new Bundle(); - final float[][] scores = onGetScores(algorithmName, algorithmArgs, actualValues, - Arrays.asList(userDataValues)); + final float[][] scores = onCalculateScores(actualValues, Arrays.asList(userDataValues), + Arrays.asList(categoryIds), defaultAlgorithm, defaultArgs, algorithms, args); if (scores != null) { data.putParcelable(EXTRA_SCORES, new Scores(scores)); } @@ -169,26 +187,111 @@ public abstract class AutofillFieldClassificationService extends Service { * @return the calculated scores of {@code actualValues} x {@code userDataValues}. * * {@hide} + * + * @deprecated Use {@link AutofillFieldClassificationService#onCalculateScores} instead. */ @Nullable @SystemApi + @Deprecated public float[][] onGetScores(@Nullable String algorithm, @Nullable Bundle algorithmOptions, @NonNull List<AutofillValue> actualValues, @NonNull List<String> userDataValues) { - Log.e(TAG, "service implementation (" + getClass() + " does not implement onGetScore()"); + Log.e(TAG, "service implementation (" + getClass() + " does not implement onGetScores()"); + return null; + } + + /** + * Calculates field classification scores in a batch. + * + * <p>A field classification score is a {@code float} representing how well an + * {@link AutofillValue} matches a expected value predicted by an autofill service + * —a full match is {@code 1.0} (representing 100%), while a full mismatch is {@code 0.0}. + * + * <p>The exact score depends on the algorithm used to calculate it—the service must + * provide at least one default algorithm (which is used when the algorithm is not specified + * or is invalid), but it could provide more (in which case the algorithm name should be + * specified by the caller when calculating the scores). + * + * <p>For example, if the service provides an algorithm named {@code EXACT_MATCH} that + * returns {@code 1.0} if all characters match or {@code 0.0} otherwise, a call to: + * + * <pre> + * HashMap algorithms = new HashMap<>(); + * algorithms.put("email", "EXACT_MATCH"); + * algorithms.put("phone", "EXACT_MATCH"); + * + * HashMap args = new HashMap<>(); + * args.put("email", null); + * args.put("phone", null); + * + * service.onCalculateScores(Arrays.asList(AutofillValue.forText("email1"), + * AutofillValue.forText("PHONE1")), Arrays.asList("email1", "phone1"), + * Array.asList("email", "phone"), algorithms, args); + * </pre> + * + * <p>Returns: + * + * <pre> + * [ + * [1.0, 0.0], // "email1" compared against ["email1", "phone1"] + * [0.0, 0.0] // "PHONE1" compared against ["email1", "phone1"] + * ]; + * </pre> + * + * <p>If the same algorithm allows the caller to specify whether the comparisons should be + * case sensitive by passing a boolean option named {@code "case_sensitive"}, then a call to: + * + * <pre> + * Bundle algorithmOptions = new Bundle(); + * algorithmOptions.putBoolean("case_sensitive", false); + * args.put("phone", algorithmOptions); + * + * service.onCalculateScores(Arrays.asList(AutofillValue.forText("email1"), + * AutofillValue.forText("PHONE1")), Arrays.asList("email1", "phone1"), + * Array.asList("email", "phone"), algorithms, args); + * </pre> + * + * <p>Returns: + * + * <pre> + * [ + * [1.0, 0.0], // "email1" compared against ["email1", "phone1"] + * [0.0, 1.0] // "PHONE1" compared against ["email1", "phone1"] + * ]; + * </pre> + * + * @param actualValues values entered by the user. + * @param userDataValues values predicted from the user data. + * @param categoryIds category Ids correspoinding to userDataValues + * @param defaultAlgorithm default field classification algorithm + * @param algorithms array of field classification algorithms + * @return the calculated scores of {@code actualValues} x {@code userDataValues}. + * + * {@hide} + */ + @Nullable + @SystemApi + public float[][] onCalculateScores(@NonNull List<AutofillValue> actualValues, + @NonNull List<String> userDataValues, @NonNull List<String> categoryIds, + @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs, + @Nullable Map algorithms, @Nullable Map args) { + Log.e(TAG, "service implementation (" + getClass() + + " does not implement onCalculateScore()"); return null; } private final class AutofillFieldClassificationServiceWrapper extends IAutofillFieldClassificationService.Stub { @Override - public void getScores(RemoteCallback callback, String algorithmName, Bundle algorithmArgs, - List<AutofillValue> actualValues, String[] userDataValues) - throws RemoteException { + public void calculateScores(RemoteCallback callback, List<AutofillValue> actualValues, + String[] userDataValues, String[] categoryIds, String defaultAlgorithm, + Bundle defaultArgs, Map algorithms, Map args) + throws RemoteException { mHandler.sendMessage(obtainMessage( - AutofillFieldClassificationService::getScores, + AutofillFieldClassificationService::calculateScores, AutofillFieldClassificationService.this, - callback, algorithmName, algorithmArgs, actualValues, userDataValues)); + callback, actualValues, userDataValues, categoryIds, defaultAlgorithm, + defaultArgs, algorithms, args)); } } diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 573d57779df7..f39ef9afdb3c 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -618,7 +618,7 @@ public abstract class AutofillService extends Service { if (SERVICE_INTERFACE.equals(intent.getAction())) { return mInterface.asBinder(); } - Log.w(TAG, "Tried to bind to wrong intent: " + intent); + Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent); return null; } diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 7bf1f83f6bd8..d408e9a6c3b1 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -84,6 +84,7 @@ public final class FillResponse implements Parcelable { private final @Nullable AutofillId[] mFieldClassificationIds; private final int mFlags; private int mRequestId; + private final @Nullable UserData mUserData; private FillResponse(@NonNull Builder builder) { mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null; @@ -99,6 +100,7 @@ public final class FillResponse implements Parcelable { mFieldClassificationIds = builder.mFieldClassificationIds; mFlags = builder.mFlags; mRequestId = INVALID_REQUEST_ID; + mUserData = builder.mUserData; } /** @hide */ @@ -157,6 +159,11 @@ public final class FillResponse implements Parcelable { } /** @hide */ + public @Nullable UserData getUserData() { + return mUserData; + } + + /** @hide */ @TestApi public int getFlags() { return mFlags; @@ -198,6 +205,7 @@ public final class FillResponse implements Parcelable { private AutofillId[] mFieldClassificationIds; private int mFlags; private boolean mDestroyed; + private UserData mUserData; /** * Triggers a custom UI before before autofilling the screen with any data set in this @@ -506,6 +514,21 @@ public final class FillResponse implements Parcelable { } /** + * Sets a specific {@link UserData} for field classification for this request only. + * + * @return this builder + * @throws IllegalStateException if the FillResponse + * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) + * requires authentication}. + */ + public Builder setUserData(@NonNull UserData userData) { + throwIfDestroyed(); + throwIfAuthenticationCalled(); + mUserData = Preconditions.checkNotNull(userData); + return this; + } + + /** * Builds a new {@link FillResponse} instance. * * @throws IllegalStateException if any of the following conditions occur: @@ -599,6 +622,9 @@ public final class FillResponse implements Parcelable { if (mFieldClassificationIds != null) { builder.append(Arrays.toString(mFieldClassificationIds)); } + if (mUserData != null) { + builder.append(", userData=").append(mUserData); + } return builder.append("]").toString(); } @@ -621,6 +647,7 @@ public final class FillResponse implements Parcelable { parcel.writeParcelable(mPresentation, flags); parcel.writeParcelable(mHeader, flags); parcel.writeParcelable(mFooter, flags); + parcel.writeParcelable(mUserData, flags); parcel.writeParcelableArray(mIgnoredIds, flags); parcel.writeLong(mDisableDuration); parcel.writeParcelableArray(mFieldClassificationIds, flags); @@ -661,6 +688,10 @@ public final class FillResponse implements Parcelable { if (footer != null) { builder.setFooter(footer); } + final UserData userData = parcel.readParcelable(null); + if (userData != null) { + builder.setUserData(userData); + } builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class)); final long disableDuration = parcel.readLong(); diff --git a/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl index 398557d5ad2e..2cd24f96a22d 100644 --- a/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl +++ b/core/java/android/service/autofill/IAutofillFieldClassificationService.aidl @@ -20,6 +20,7 @@ import android.os.Bundle; import android.os.RemoteCallback; import android.view.autofill.AutofillValue; import java.util.List; +import java.util.Map; /** * Service used to calculate match scores for Autofill Field Classification. @@ -27,6 +28,8 @@ import java.util.List; * @hide */ oneway interface IAutofillFieldClassificationService { - void getScores(in RemoteCallback callback, String algorithmName, in Bundle algorithmArgs, - in List<AutofillValue> actualValues, in String[] userDataValues); + void calculateScores(in RemoteCallback callback, in List<AutofillValue> actualValues, + in String[] userDataValues, in String[] categoryIds, + in String defaultAlgorithm, in Bundle defaultArgs, + in Map algorithms, in Map args); } diff --git a/core/java/android/service/autofill/UserData.java b/core/java/android/service/autofill/UserData.java index fccb85b957fa..37f192366f81 100644 --- a/core/java/android/service/autofill/UserData.java +++ b/core/java/android/service/autofill/UserData.java @@ -24,6 +24,7 @@ import static android.view.autofill.Helper.sDebug; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.app.ActivityThread; import android.content.ContentResolver; import android.os.Bundle; @@ -32,6 +33,7 @@ import android.os.Parcelable; import android.provider.Settings; import android.service.autofill.FieldClassification.Match; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.view.autofill.AutofillManager; @@ -57,28 +59,57 @@ public final class UserData implements Parcelable { private static final int DEFAULT_MAX_VALUE_LENGTH = 100; private final String mId; - private final String mAlgorithm; - private final Bundle mAlgorithmArgs; private final String[] mCategoryIds; private final String[] mValues; + private final String mDefaultAlgorithm; + private final Bundle mDefaultArgs; + private final ArrayMap<String, String> mCategoryAlgorithms; + private final ArrayMap<String, Bundle> mCategoryArgs; + private UserData(Builder builder) { mId = builder.mId; - mAlgorithm = builder.mAlgorithm; - mAlgorithmArgs = builder.mAlgorithmArgs; mCategoryIds = new String[builder.mCategoryIds.size()]; builder.mCategoryIds.toArray(mCategoryIds); mValues = new String[builder.mValues.size()]; builder.mValues.toArray(mValues); + builder.mValues.toArray(mValues); + + mDefaultAlgorithm = builder.mDefaultAlgorithm; + mDefaultArgs = builder.mDefaultArgs; + mCategoryAlgorithms = builder.mCategoryAlgorithms; + mCategoryArgs = builder.mCategoryArgs; } /** - * Gets the name of the algorithm that is used to calculate - * {@link Match#getScore() match scores}. + * Gets the name of the default algorithm that is used to calculate + * {@link Match#getScore()} match scores}. */ @Nullable public String getFieldClassificationAlgorithm() { - return mAlgorithm; + return mDefaultAlgorithm; + } + + /** @hide */ + public Bundle getDefaultFieldClassificationArgs() { + return mDefaultArgs; + } + + /** + * Gets the name of the algorithm corresponding to the specific autofill category + * that is used to calculate {@link Match#getScore() match scores} + * + * @param categoryId autofill field category + * + * @return String name of algorithm, null if none found. + */ + @Nullable + public String getFieldClassificationAlgorithmForCategory(@NonNull String categoryId) { + Preconditions.checkNotNull(categoryId); + if (mCategoryAlgorithms == null || !mCategoryAlgorithms.containsKey(categoryId)) { + return null; + } + return mCategoryAlgorithms.get(categoryId); } /** @@ -89,11 +120,6 @@ public final class UserData implements Parcelable { } /** @hide */ - public Bundle getAlgorithmArgs() { - return mAlgorithmArgs; - } - - /** @hide */ public String[] getCategoryIds() { return mCategoryIds; } @@ -104,11 +130,29 @@ public final class UserData implements Parcelable { } /** @hide */ + @TestApi + public ArrayMap<String, String> getFieldClassificationAlgorithms() { + return mCategoryAlgorithms; + } + + /** @hide */ + public ArrayMap<String, Bundle> getFieldClassificationArgs() { + return mCategoryArgs; + } + + /** @hide */ public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("id: "); pw.print(mId); - pw.print(prefix); pw.print("Algorithm: "); pw.print(mAlgorithm); - pw.print(" Args: "); pw.println(mAlgorithmArgs); - + pw.print(prefix); pw.print("Default Algorithm: "); pw.print(mDefaultAlgorithm); + pw.print(prefix); pw.print("Default Args"); pw.print(mDefaultArgs); + if (mCategoryAlgorithms != null && mCategoryAlgorithms.size() > 0) { + pw.print(prefix); pw.print("Algorithms per category: "); + for (int i = 0; i < mCategoryAlgorithms.size(); i++) { + pw.print(prefix); pw.print(prefix); pw.print(mCategoryAlgorithms.keyAt(i)); + pw.print(": "); pw.println(Helper.getRedacted(mCategoryAlgorithms.valueAt(i))); + pw.print("args="); pw.print(mCategoryArgs.get(mCategoryAlgorithms.keyAt(i))); + } + } // Cannot disclose field ids or values because they could contain PII pw.print(prefix); pw.print("Field ids size: "); pw.println(mCategoryIds.length); for (int i = 0; i < mCategoryIds.length; i++) { @@ -139,8 +183,13 @@ public final class UserData implements Parcelable { private final String mId; private final ArrayList<String> mCategoryIds; private final ArrayList<String> mValues; - private String mAlgorithm; - private Bundle mAlgorithmArgs; + private String mDefaultAlgorithm; + private Bundle mDefaultArgs; + + // Map of autofill field categories to fleid classification algorithms and args + private ArrayMap<String, String> mCategoryAlgorithms; + private ArrayMap<String, Bundle> mCategoryArgs; + private boolean mDestroyed; // Non-persistent array used to limit the number of unique ids. @@ -148,7 +197,6 @@ public final class UserData implements Parcelable { // Non-persistent array used to ignore duplaicated value/category pairs. private final ArraySet<String> mUniqueValueCategoryPairs; - /** * Creates a new builder for the user data used for <a href="#FieldClassification">field * classification</a>. @@ -169,7 +217,7 @@ public final class UserData implements Parcelable { * {@link AutofillManager#getUserData()}). * * @param value value of the user data. - * @param categoryId string used to identify the category the value is associated with. + * @param categoryId autofill field category. * * @throws IllegalArgumentException if any of the following occurs: * <ul> @@ -189,13 +237,15 @@ public final class UserData implements Parcelable { mCategoryIds = new ArrayList<>(maxUserDataSize); mValues = new ArrayList<>(maxUserDataSize); mUniqueValueCategoryPairs = new ArraySet<>(maxUserDataSize); + mUniqueCategoryIds = new ArraySet<>(getMaxCategoryCount()); addMapping(value, categoryId); } /** - * Sets the algorithm used for <a href="#FieldClassification">field classification</a>. + * Sets the default algorithm used for + * <a href="#FieldClassification">field classification</a>. * * <p>The currently available algorithms can be retrieve through * {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}. @@ -212,8 +262,40 @@ public final class UserData implements Parcelable { public Builder setFieldClassificationAlgorithm(@Nullable String name, @Nullable Bundle args) { throwIfDestroyed(); - mAlgorithm = name; - mAlgorithmArgs = args; + mDefaultAlgorithm = name; + mDefaultArgs = args; + return this; + } + + /** + * Sets the algorithm used for <a href="#FieldClassification">field classification</a> + * for the specified category. + * + * <p>The currently available algorithms can be retrieved through + * {@link AutofillManager#getAvailableFieldClassificationAlgorithms()}. + * + * <p>If not set, the + * {@link AutofillManager#getDefaultFieldClassificationAlgorithm() default algorithm} is + * used instead. + * + * @param categoryId autofill field category. + * @param name name of the algorithm or {@code null} to used default. + * @param args optional arguments to the algorithm. + * + * @return this builder + */ + public Builder setFieldClassificationAlgorithmForCategory(@NonNull String categoryId, + @Nullable String name, @Nullable Bundle args) { + throwIfDestroyed(); + Preconditions.checkNotNull(categoryId); + if (mCategoryAlgorithms == null) { + mCategoryAlgorithms = new ArrayMap<>(getMaxCategoryCount()); + } + if (mCategoryArgs == null) { + mCategoryArgs = new ArrayMap<>(getMaxCategoryCount()); + } + mCategoryAlgorithms.put(categoryId, name); + mCategoryArgs.put(categoryId, args); return this; } @@ -317,8 +399,7 @@ public final class UserData implements Parcelable { public String toString() { if (!sDebug) return super.toString(); - final StringBuilder builder = new StringBuilder("UserData: [id=").append(mId) - .append(", algorithm=").append(mAlgorithm); + final StringBuilder builder = new StringBuilder("UserData: [id=").append(mId); // Cannot disclose category ids or values because they could contain PII builder.append(", categoryIds="); Helper.appendRedacted(builder, mCategoryIds); @@ -341,8 +422,10 @@ public final class UserData implements Parcelable { parcel.writeString(mId); parcel.writeStringArray(mCategoryIds); parcel.writeStringArray(mValues); - parcel.writeString(mAlgorithm); - parcel.writeBundle(mAlgorithmArgs); + parcel.writeString(mDefaultAlgorithm); + parcel.writeBundle(mDefaultArgs); + parcel.writeMap(mCategoryAlgorithms); + parcel.writeMap(mCategoryArgs); } public static final Parcelable.Creator<UserData> CREATOR = @@ -355,10 +438,28 @@ public final class UserData implements Parcelable { final String id = parcel.readString(); final String[] categoryIds = parcel.readStringArray(); final String[] values = parcel.readStringArray(); + final String defaultAlgorithm = parcel.readString(); + final Bundle defaultArgs = parcel.readBundle(); + final ArrayMap<String, String> categoryAlgorithms = new ArrayMap<>(); + parcel.readMap(categoryAlgorithms, String.class.getClassLoader()); + final ArrayMap<String, Bundle> categoryArgs = new ArrayMap<>(); + parcel.readMap(categoryArgs, Bundle.class.getClassLoader()); + final Builder builder = new Builder(id, values[0], categoryIds[0]) - .setFieldClassificationAlgorithm(parcel.readString(), parcel.readBundle()); + .setFieldClassificationAlgorithm(defaultAlgorithm, defaultArgs); + for (int i = 1; i < categoryIds.length; i++) { - builder.add(values[i], categoryIds[i]); + String categoryId = categoryIds[i]; + builder.add(values[i], categoryId); + } + + final int size = categoryAlgorithms.size(); + if (size > 0) { + for (int i = 0; i < size; i++) { + final String categoryId = categoryAlgorithms.keyAt(i); + builder.setFieldClassificationAlgorithmForCategory(categoryId, + categoryAlgorithms.valueAt(i), categoryArgs.get(categoryId)); + } } return builder.build(); } diff --git a/core/java/android/service/intelligence/SmartSuggestionsService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index b684b0208d8d..9aff281b0472 100644 --- a/core/java/android/service/intelligence/SmartSuggestionsService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.intelligence; +package android.service.autofill.augmented; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -32,16 +32,15 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; -import android.service.intelligence.PresentationParams.SystemPopupPresentationParams; -import android.util.ArrayMap; +import android.service.autofill.augmented.PresentationParams.SystemPopupPresentationParams; import android.util.Log; import android.util.Pair; import android.util.Slog; +import android.util.SparseArray; import android.util.TimeUtils; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; import android.view.autofill.IAugmentedAutofillManagerClient; -import android.view.intelligence.ContentCaptureEvent; import com.android.internal.annotations.GuardedBy; @@ -51,18 +50,17 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; -import java.util.Set; /** - * A service used to capture the content of the screen to provide contextual data in other areas of - * the system such as Autofill. + * A service used to augment the Autofill subsystem by potentially providing autofill data when the + * "standard" workflow failed (for example, because the standard AutofillService didn't have data). * * @hide */ @SystemApi -public abstract class SmartSuggestionsService extends Service { +public abstract class AugmentedAutofillService extends Service { - private static final String TAG = "SmartSuggestionsService"; + private static final String TAG = AugmentedAutofillService.class.getSimpleName(); // TODO(b/111330312): STOPSHIP use dynamic value, or change to false static final boolean DEBUG = true; @@ -71,63 +69,32 @@ public abstract class SmartSuggestionsService extends Service { /** * The {@link Intent} that must be declared as handled by the service. * To be supported, the service must also require the - * {@link android.Manifest.permission#BIND_SMART_SUGGESTIONS_SERVICE} permission so + * {@link android.Manifest.permission#BIND_AUGMENTED_AUTOFILL_SERVICE} permission so * that other applications can not abuse it. */ public static final String SERVICE_INTERFACE = - "android.service.intelligence.SmartSuggestionsService"; + "android.service.autofill.augmented.AugmentedAutofillService"; private Handler mHandler; - private ArrayMap<InteractionSessionId, AutofillProxy> mAutofillProxies; + private SparseArray<AutofillProxy> mAutofillProxies; - private final IIntelligenceService mInterface = new IIntelligenceService.Stub() { + private final IAugmentedAutofillService mInterface = new IAugmentedAutofillService.Stub() { @Override - public void onSessionLifecycle(InteractionContext context, InteractionSessionId sessionId) - throws RemoteException { - if (context != null) { - mHandler.sendMessage( - obtainMessage(SmartSuggestionsService::onCreateInteractionSession, - SmartSuggestionsService.this, context, sessionId)); - } else { - mHandler.sendMessage( - obtainMessage(SmartSuggestionsService::onDestroyInteractionSession, - SmartSuggestionsService.this, sessionId)); - } - } - - @Override - public void onContentCaptureEventsRequest(InteractionSessionId sessionId, - ContentCaptureEventsRequest request) { - mHandler.sendMessage( - obtainMessage(SmartSuggestionsService::onContentCaptureEventsRequest, - SmartSuggestionsService.this, sessionId, request)); - - } - - @Override - public void onActivitySnapshot(InteractionSessionId sessionId, - SnapshotData snapshotData) { - mHandler.sendMessage( - obtainMessage(SmartSuggestionsService::onActivitySnapshot, - SmartSuggestionsService.this, sessionId, snapshotData)); - } - - @Override - public void onAutofillRequest(InteractionSessionId sessionId, IBinder client, - int autofilSessionId, AutofillId focusedId, AutofillValue focusedValue, - long requestTime) { - mHandler.sendMessage(obtainMessage(SmartSuggestionsService::handleOnAutofillRequest, - SmartSuggestionsService.this, sessionId, client, autofilSessionId, focusedId, - focusedValue, requestTime)); + public void onFillRequest(int sessionId, IBinder client, int taskId, + ComponentName componentName, AutofillId focusedId, AutofillValue focusedValue, + long requestTime, IFillCallback callback) { + mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnFillRequest, + AugmentedAutofillService.this, sessionId, client, taskId, componentName, + focusedId, focusedValue, requestTime, callback)); } @Override - public void onDestroyAutofillWindowsRequest(InteractionSessionId sessionId) { + public void onDestroyFillWindowRequest(int sessionId) { mHandler.sendMessage( - obtainMessage(SmartSuggestionsService::handleOnDestroyAutofillWindowsRequest, - SmartSuggestionsService.this, sessionId)); + obtainMessage(AugmentedAutofillService::handleOnDestroyFillWindowRequest, + AugmentedAutofillService.this, sessionId)); } }; @@ -144,118 +111,18 @@ public abstract class SmartSuggestionsService extends Service { if (SERVICE_INTERFACE.equals(intent.getAction())) { return mInterface.asBinder(); } - Log.w(TAG, "Tried to bind to wrong intent: " + intent); + Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent); return null; } - /** - * Explicitly limits content capture to the given packages and activities. - * - * <p>When the whitelist is set, it overrides the values passed to - * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)} - * and {@link #setPackageContentCaptureEnabled(String, boolean)}. - * - * <p>To reset the whitelist, call it passing {@code null} to both arguments. - * - * <p>Useful when the service wants to restrict content capture to a category of apps, like - * chat apps. For example, if the service wants to support view captures on all activities of - * app {@code ChatApp1} and just activities {@code act1} and {@code act2} of {@code ChatApp2}, - * it would call: {@code setContentCaptureWhitelist(Arrays.asList("ChatApp1"), - * Arrays.asList(new ComponentName("ChatApp2", "act1"), - * new ComponentName("ChatApp2", "act2")));} - */ - public final void setContentCaptureWhitelist(@Nullable List<String> packages, - @Nullable List<ComponentName> activities) { - //TODO(b/111276913): implement - } - - /** - * Defines whether content capture should be enabled for activities with such - * {@link android.content.ComponentName}. - * - * <p>Useful to blacklist a particular activity. - */ - public final void setActivityContentCaptureEnabled(@NonNull ComponentName activity, - boolean enabled) { - //TODO(b/111276913): implement - } - - /** - * Defines whether content capture should be enabled for activities of the app with such - * {@code packageName}. - * - * <p>Useful to blacklist any activity from a particular app. - */ - public final void setPackageContentCaptureEnabled(@NonNull String packageName, - boolean enabled) { - //TODO(b/111276913): implement - } - - /** - * Gets the activities where content capture was disabled by - * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)}. - */ - @NonNull - public final Set<ComponentName> getContentCaptureDisabledActivities() { - //TODO(b/111276913): implement - return null; - } - - /** - * Gets the apps where content capture was disabled by - * {@link #setPackageContentCaptureEnabled(String, boolean)}. - */ - @NonNull - public final Set<String> getContentCaptureDisabledPackages() { - //TODO(b/111276913): implement - return null; - } - - /** - * Creates a new interaction session. - * - * @param context interaction context - * @param sessionId the session's Id - */ - public void onCreateInteractionSession(@NonNull InteractionContext context, - @NonNull InteractionSessionId sessionId) { - if (VERBOSE) { - Log.v(TAG, "onCreateInteractionSession(id=" + sessionId + ", ctx=" + context + ")"); - } + @Override + public boolean onUnbind(Intent intent) { + mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnUnbind, + AugmentedAutofillService.this)); + return false; } - /** - * Notifies the service of {@link ContentCaptureEvent events} associated with a content capture - * session. - * - * @param sessionId the session's Id - * @param request the events - */ - // TODO(b/111276913): rename to onContentCaptureEvents or something like that; also, pass a - // Request object so it can be extended - public abstract void onContentCaptureEventsRequest(@NonNull InteractionSessionId sessionId, - @NonNull ContentCaptureEventsRequest request); - - private void handleOnAutofillRequest(@NonNull InteractionSessionId sessionId, - @NonNull IBinder client, int autofillSessionId, @NonNull AutofillId focusedId, - @Nullable AutofillValue focusedValue, long requestTime) { - if (mAutofillProxies == null) { - mAutofillProxies = new ArrayMap<>(); - } - AutofillProxy proxy = mAutofillProxies.get(sessionId); - if (proxy == null) { - proxy = new AutofillProxy(sessionId, client, autofillSessionId, focusedId, focusedValue, - requestTime); - mAutofillProxies.put(sessionId, proxy); - } else { - // TODO(b/111330312): figure out if it's ok to reuse the proxy; add logging - if (DEBUG) Log.d(TAG, "Reusing proxy for session " + sessionId); - } - // TODO(b/111330312): set cancellation signal - final CancellationSignal cancellationSignal = null; - onFillRequest(sessionId, new FillRequest(proxy), cancellationSignal, - new FillController(proxy), new FillCallback(proxy)); - } + // TODO(b/111330312): add methods to disable autofill per app / activity? /** * Asks the service to handle an "augmented" autofill request. @@ -275,24 +142,46 @@ public abstract class SmartSuggestionsService extends Service { * {@link FillController#autofill(List)} method). * * <p>The service <b>MUST</b> call {@link - * FillCallback#onSuccess(android.service.intelligence.FillResponse)} as soon as possible, + * FillCallback#onSuccess(android.service.autofill.augmented.FillResponse)} as soon as possible, * passing {@code null} when it cannot fulfill the request. - * - * @param sessionId the session's id * @param request the request to handle. * @param cancellationSignal signal for observing cancellation requests. The system will use * this to notify you that the fill result is no longer needed and you should stop * handling this fill request in order to save resources. * @param controller object used to interact with the autofill system. * @param callback object used to notify the result of the request. Service <b>must</b> call - * {@link FillCallback#onSuccess(android.service.intelligence.FillResponse)}. + * {@link FillCallback#onSuccess(android.service.autofill.augmented.FillResponse)}. */ - public void onFillRequest(@NonNull InteractionSessionId sessionId, @NonNull FillRequest request, + public void onFillRequest(@NonNull FillRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull FillController controller, @NonNull FillCallback callback) { } - private void handleOnDestroyAutofillWindowsRequest(@NonNull InteractionSessionId sessionId) { + private void handleOnFillRequest(int sessionId, @NonNull IBinder client, int taskId, + @NonNull ComponentName componentName, @NonNull AutofillId focusedId, + @Nullable AutofillValue focusedValue, long requestTime, + @NonNull IFillCallback callback) { + if (mAutofillProxies == null) { + mAutofillProxies = new SparseArray<>(); + } + AutofillProxy proxy = mAutofillProxies.get(sessionId); + if (proxy == null) { + proxy = new AutofillProxy(sessionId, client, taskId, componentName, focusedId, + focusedValue, requestTime, callback); + mAutofillProxies.put(sessionId, proxy); + } else { + // TODO(b/111330312): figure out if it's ok to reuse the proxy; add logging + // TODO(b/111330312): also make sure to cover scenario on CTS test + if (DEBUG) Log.d(TAG, "Reusing proxy for session " + sessionId); + proxy.update(focusedId, focusedValue); + } + // TODO(b/111330312): set cancellation signal + final CancellationSignal cancellationSignal = null; + onFillRequest(new FillRequest(proxy), cancellationSignal, new FillController(proxy), + new FillCallback(proxy)); + } + + private void handleOnDestroyFillWindowRequest(@NonNull int sessionId) { AutofillProxy proxy = null; if (mAutofillProxies != null) { proxy = mAutofillProxies.get(sessionId); @@ -306,13 +195,31 @@ public abstract class SmartSuggestionsService extends Service { mAutofillProxies.remove(sessionId); } + private void handleOnUnbind() { + if (mAutofillProxies == null) { + if (DEBUG) Log.d(TAG, "onUnbind(): no proxy to destroy"); + return; + } + final int size = mAutofillProxies.size(); + if (DEBUG) Log.d(TAG, "onUnbind(): destroying " + size + " proxies"); + for (int i = 0; i < size; i++) { + final AutofillProxy proxy = mAutofillProxies.valueAt(i); + try { + proxy.destroy(); + } catch (Exception e) { + Log.w(TAG, "error destroying " + proxy); + } + } + mAutofillProxies = null; + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mAutofillProxies != null) { final int size = mAutofillProxies.size(); pw.print("Number proxies: "); pw.println(size); for (int i = 0; i < size; i++) { - final InteractionSessionId sessionId = mAutofillProxies.keyAt(i); + final int sessionId = mAutofillProxies.keyAt(i); final AutofillProxy proxy = mAutofillProxies.valueAt(i); pw.print(i); pw.print(") SessionId="); pw.print(sessionId); pw.println(":"); proxy.dump(" ", pw); @@ -320,26 +227,6 @@ public abstract class SmartSuggestionsService extends Service { } } - /** - * Notifies the service of {@link SnapshotData snapshot data} associated with a session. - * - * @param sessionId the session's Id - * @param snapshotData the data - */ - public void onActivitySnapshot(@NonNull InteractionSessionId sessionId, - @NonNull SnapshotData snapshotData) {} - - /** - * Destroys the interaction session. - * - * @param sessionId the id of the session to destroy - */ - public void onDestroyInteractionSession(@NonNull InteractionSessionId sessionId) { - if (VERBOSE) { - Log.v(TAG, "onDestroyInteractionSession(id=" + sessionId + ")"); - } - } - /** @hide */ static final class AutofillProxy { @@ -358,10 +245,14 @@ public abstract class SmartSuggestionsService extends Service { private final Object mLock = new Object(); private final IAugmentedAutofillManagerClient mClient; - private final int mAutofillSessionId; - public final InteractionSessionId sessionId; - public final AutofillId focusedId; - public final AutofillValue focusedValue; + private final int mSessionId; + private final IFillCallback mCallback; + public final int taskId; + public final ComponentName componentName; + @GuardedBy("mLock") + private AutofillId mFocusedId; + @GuardedBy("mLock") + private AutofillValue mFocusedValue; // Objects used to log metrics private final long mRequestTime; @@ -375,14 +266,17 @@ public abstract class SmartSuggestionsService extends Service { @GuardedBy("mLock") private FillWindow mFillWindow; - private AutofillProxy(@NonNull InteractionSessionId sessionId, @NonNull IBinder client, - int autofillSessionId, @NonNull AutofillId focusedId, - @Nullable AutofillValue focusedValue, long requestTime) { - this.sessionId = sessionId; + private AutofillProxy(int sessionId, @NonNull IBinder client, int taskId, + @NonNull ComponentName componentName, @NonNull AutofillId focusedId, + @Nullable AutofillValue focusedValue, long requestTime, + @NonNull IFillCallback callback) { + mSessionId = sessionId; mClient = IAugmentedAutofillManagerClient.Stub.asInterface(client); - mAutofillSessionId = autofillSessionId; - this.focusedId = focusedId; - this.focusedValue = focusedValue; + mCallback = callback; + this.taskId = taskId; + this.componentName = componentName; + this.mFocusedId = focusedId; + this.mFocusedValue = focusedValue; this.mRequestTime = requestTime; // TODO(b/111330312): linkToDeath } @@ -395,13 +289,13 @@ public abstract class SmartSuggestionsService extends Service { } Rect rect; try { - rect = mClient.getViewCoordinates(focusedId); + rect = mClient.getViewCoordinates(mFocusedId); } catch (RemoteException e) { - Log.w(TAG, "Could not get coordinates for " + focusedId); + Log.w(TAG, "Could not get coordinates for " + mFocusedId); return null; } if (rect == null) { - if (DEBUG) Log.d(TAG, "getViewCoordinates(" + focusedId + ") returned null"); + if (DEBUG) Log.d(TAG, "getViewCoordinates(" + mFocusedId + ") returned null"); return null; } mSmartSuggestion = new SystemPopupPresentationParams(this, rect); @@ -419,7 +313,7 @@ public abstract class SmartSuggestionsService extends Service { ids.add(pair.first); values.add(pair.second); } - mClient.autofill(mAutofillSessionId, ids, values); + mClient.autofill(mSessionId, ids, values); } public void setFillWindow(@NonNull FillWindow fillWindow) { @@ -434,7 +328,29 @@ public abstract class SmartSuggestionsService extends Service { } } - // Used for metrics. + private void update(@NonNull AutofillId focusedId, @NonNull AutofillValue focusedValue) { + synchronized (mLock) { + // TODO(b/111330312): should we close the popupwindow if the focused id changed? + mFocusedId = focusedId; + mFocusedValue = focusedValue; + } + } + + @NonNull + public AutofillId getFocusedId() { + synchronized (mLock) { + return mFocusedId; + } + } + + @NonNull + public AutofillValue getFocusedValue() { + synchronized (mLock) { + return mFocusedValue; + } + } + + // Used (mostly) for metrics. public void report(@ReportEvent int event) { switch (event) { case REPORT_EVENT_ON_SUCCESS: @@ -445,6 +361,11 @@ public abstract class SmartSuggestionsService extends Service { + TimeUtils.formatDuration(mOnSuccessTime - mRequestTime)); } } + try { + mCallback.onSuccess(); + } catch (RemoteException e) { + Log.e(TAG, "Error reporting success: " + e); + } break; case REPORT_EVENT_UI_SHOWN: if (mUiFirstShownTime == 0) { @@ -471,12 +392,14 @@ public abstract class SmartSuggestionsService extends Service { // TODO(b/111330312): log metrics as well } - public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { - pw.print(prefix); pw.print("afSessionId: "); pw.println(mAutofillSessionId); - pw.print(prefix); pw.print("focusedId: "); pw.println(focusedId); - if (focusedValue != null) { - pw.print(prefix); pw.print("focusedValue: "); pw.println(focusedValue); + pw.print(prefix); pw.print("sessionId: "); pw.println(mSessionId); + pw.print(prefix); pw.print("taskId: "); pw.println(taskId); + pw.print(prefix); pw.print("component: "); + pw.println(componentName.flattenToShortString()); + pw.print(prefix); pw.print("focusedId: "); pw.println(mFocusedId); + if (mFocusedValue != null) { + pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue); } pw.print(prefix); pw.print("client: "); pw.println(mClient); final String prefix2 = prefix + " "; diff --git a/core/java/android/service/intelligence/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index ddf37f737296..620ec59c9e55 100644 --- a/core/java/android/service/intelligence/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -13,12 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.intelligence; +package android.service.autofill.augmented; + +import static android.service.autofill.augmented.AugmentedAutofillService.DEBUG; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.service.intelligence.SmartSuggestionsService.AutofillProxy; +import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy; +import android.util.Log; /** * Callback used to indicate at {@link FillRequest} has been fulfilled. @@ -27,6 +30,9 @@ import android.service.intelligence.SmartSuggestionsService.AutofillProxy; */ @SystemApi public final class FillCallback { + + private static final String TAG = FillCallback.class.getSimpleName(); + private final AutofillProxy mProxy; FillCallback(@NonNull AutofillProxy proxy) { @@ -40,7 +46,11 @@ public final class FillCallback { * could not provide autofill for the request. */ public void onSuccess(@Nullable FillResponse response) { + if (DEBUG) Log.d(TAG, "onSuccess(): " + response); + mProxy.report(AutofillProxy.REPORT_EVENT_ON_SUCCESS); + if (response == null) return; + final FillWindow fillWindow = response.getFillWindow(); if (fillWindow != null) { fillWindow.show(); diff --git a/core/java/android/service/intelligence/FillController.java b/core/java/android/service/autofill/augmented/FillController.java index 4a9c85de1fa2..e65cf47e1645 100644 --- a/core/java/android/service/intelligence/FillController.java +++ b/core/java/android/service/autofill/augmented/FillController.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.intelligence; +package android.service.autofill.augmented; -import static android.service.intelligence.SmartSuggestionsService.DEBUG; +import static android.service.autofill.augmented.AugmentedAutofillService.DEBUG; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.RemoteException; -import android.service.intelligence.SmartSuggestionsService.AutofillProxy; +import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy; import android.util.Log; import android.util.Pair; import android.view.autofill.AutofillId; diff --git a/core/java/android/service/intelligence/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java index 53e99a5fd3b8..57d2cc85f7ea 100644 --- a/core/java/android/service/intelligence/FillRequest.java +++ b/core/java/android/service/autofill/augmented/FillRequest.java @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.intelligence; +package android.service.autofill.augmented; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.service.intelligence.SmartSuggestionsService.AutofillProxy; +import android.content.ComponentName; +import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; @@ -27,6 +28,7 @@ import android.view.autofill.AutofillValue; * @hide */ @SystemApi +// TODO(b/111330312): pass a requestId and/or sessionId public final class FillRequest { final AutofillProxy mProxy; @@ -37,11 +39,18 @@ public final class FillRequest { } /** - * Gets the session associated with this request. + * Gets the task of the activity associated with this request. + */ + public int getTaskId() { + return mProxy.taskId; + } + + /** + * Gets the name of the activity associated with this request. */ @NonNull - public InteractionSessionId getSessionId() { - return mProxy.sessionId; + public ComponentName getActivityComponent() { + return mProxy.componentName; } /** @@ -49,15 +58,15 @@ public final class FillRequest { */ @NonNull public AutofillId getFocusedId() { - return mProxy.focusedId; + return mProxy.getFocusedId(); } /** * Gets the current value of the field that triggered the request. */ @NonNull - public AutofillValue getFocusedAutofillValue() { - return mProxy.focusedValue; + public AutofillValue getFocusedValue() { + return mProxy.getFocusedValue(); } /** @@ -72,6 +81,7 @@ public final class FillRequest { @Override public String toString() { - return "FillRequest[id=" + mProxy.focusedId + "]"; + return "FillRequest[act=" + getActivityComponent().flattenToShortString() + + ", id=" + mProxy.getFocusedId() + "]"; } } diff --git a/core/java/android/service/intelligence/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java index 860c0275732a..1ecfab4edc12 100644 --- a/core/java/android/service/intelligence/FillResponse.java +++ b/core/java/android/service/autofill/augmented/FillResponse.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.intelligence; +package android.service.autofill.augmented; import android.annotation.NonNull; import android.annotation.Nullable; @@ -50,7 +50,7 @@ public final class FillResponse implements Parcelable { * @hide */ @SystemApi - public static class Builder { + public static final class Builder { private FillWindow mFillWindow; @@ -102,6 +102,8 @@ public final class FillResponse implements Parcelable { // TODO(b/111330312): add methods to disable app / activity, either here or on manager } + // TODO(b/111330312): implement to String + @Override public int describeContents() { return 0; diff --git a/core/java/android/service/intelligence/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java index 39d7e08a68d9..27df845ca3e0 100644 --- a/core/java/android/service/intelligence/FillWindow.java +++ b/core/java/android/service/autofill/augmented/FillWindow.java @@ -13,19 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.intelligence; +package android.service.autofill.augmented; -import static android.service.intelligence.SmartSuggestionsService.DEBUG; +import static android.service.autofill.augmented.AugmentedAutofillService.DEBUG; import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.app.Dialog; import android.graphics.Rect; -import android.service.intelligence.PresentationParams.Area; -import android.service.intelligence.SmartSuggestionsService.AutofillProxy; +import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy; +import android.service.autofill.augmented.PresentationParams.Area; import android.util.Log; import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -60,7 +61,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ @SystemApi -public final class FillWindow { +public final class FillWindow implements AutoCloseable { private static final String TAG = "FillWindow"; /** Indicates the data being shown is a physical address */ @@ -140,10 +141,24 @@ public final class FillWindow { // TODO(b/111330312): make sure all touch events are handled, window is always closed, // etc. - mDialog = new Dialog(rootView.getContext()); + mDialog = new Dialog(rootView.getContext()) { + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { + FillWindow.this.destroy(); + } + return false; + } + }; mCloseGuard.open("destroy"); final Window window = mDialog.getWindow(); window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); + // Makes sure touch outside the dialog is received by the window behind the dialog. + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); + // Makes sure the touch outside the dialog is received by the dialog to dismiss it. + window.addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); + // Makes sure keyboard shows up. + window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); final int height = rect.bottom - rect.top; final int width = rect.right - rect.left; @@ -238,4 +253,10 @@ public final class FillWindow { } } } + + /** @hide */ + @Override + public void close() throws Exception { + destroy(); + } } diff --git a/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl b/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl new file mode 100644 index 000000000000..b3ac2da1c17e --- /dev/null +++ b/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill.augmented; + +import android.content.ComponentName; +import android.os.IBinder; + +import android.service.autofill.augmented.IFillCallback; +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; + +import java.util.List; + +/** + * Interface from the system to an Augmented Autofill service. + * + * @hide + */ +oneway interface IAugmentedAutofillService { + + void onFillRequest(int sessionId, in IBinder autofillManagerClient, int taskId, + in ComponentName activityComponent, in AutofillId focusedId, + in AutofillValue focusedValue, long requestTime, in IFillCallback callback); + + void onDestroyFillWindowRequest(int sessionId); +} diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl new file mode 100644 index 000000000000..dac75904f4fc --- /dev/null +++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill.augmented; + +import android.os.ICancellationSignal; + +/** + * Interface to receive the result of an autofill request. + * + * @hide + */ +interface IFillCallback { + // TODO(b/111330312): add cancellation (after we have CTS tests, so we can test it) +// void onCancellable(in ICancellationSignal cancellation); + // TODO(b/111330312): might need to pass the response (once IME implements Smart Suggestions) + void onSuccess(); +} diff --git a/core/java/android/service/intelligence/PresentationParams.java b/core/java/android/service/autofill/augmented/PresentationParams.java index 95303099793a..0124ecca740d 100644 --- a/core/java/android/service/intelligence/PresentationParams.java +++ b/core/java/android/service/autofill/augmented/PresentationParams.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.intelligence; +package android.service.autofill.augmented; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.graphics.Rect; -import android.service.intelligence.SmartSuggestionsService.AutofillProxy; +import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy; import android.util.DebugUtils; import android.view.View; @@ -174,11 +174,12 @@ public abstract class PresentationParams { * * @param bounds boundaries relative to this Area. * - * @throws {@link IllegalArgumentException} if the {@code bounds} is not fully-contained - * inside this full Area. - * * @return new subarea, or {@code null} if the Smart Suggestion host does not support such * subaarea. + * + * @throws IllegalArgumentException if the {@code bounds} is not fully-contained inside this + * full Area. + * */ @Nullable public Area getSubArea(@NonNull Rect bounds) { diff --git a/core/java/android/service/carrier/CarrierIdentifier.java b/core/java/android/service/carrier/CarrierIdentifier.java index e930f401ecd5..568ca0f6b56e 100644 --- a/core/java/android/service/carrier/CarrierIdentifier.java +++ b/core/java/android/service/carrier/CarrierIdentifier.java @@ -71,10 +71,8 @@ public class CarrierIdentifier implements Parcelable { * @param gid2 group id level 2 * @param carrierid carrier unique identifier {@link TelephonyManager#getSimCarrierId()}, used * to uniquely identify the carrier and look up the carrier configurations. - * @param preciseCarrierId precise carrier identifier {@link TelephonyManager#getSimPreciseCarrierId()} - * @hide - * - * TODO: expose this to public API + * @param preciseCarrierId precise carrier identifier + * {@link TelephonyManager#getSimPreciseCarrierId()} */ public CarrierIdentifier(String mcc, String mnc, @Nullable String spn, @Nullable String imsi, @Nullable String gid1, @Nullable String gid2, @@ -155,16 +153,16 @@ public class CarrierIdentifier implements Parcelable { } /** - * Get the carrier id {@link TelephonyManager#getSimCarrierId() } - * @hide + * Returns the carrier id. + * @see TelephonyManager#getSimCarrierId() */ public int getCarrierId() { return mCarrierId; } /** - * Get the precise carrier id {@link TelephonyManager#getSimPreciseCarrierId()} - * @hide + * Returns the precise carrier id. + * @see TelephonyManager#getSimPreciseCarrierId() */ public int getPreciseCarrierId() { return mPreciseCarrierId; diff --git a/core/java/android/service/intelligence/ContentCaptureEventsRequest.aidl b/core/java/android/service/contentcapture/ContentCaptureEventsRequest.aidl index 23d607d9838c..c032cfdb9677 100644 --- a/core/java/android/service/intelligence/ContentCaptureEventsRequest.aidl +++ b/core/java/android/service/contentcapture/ContentCaptureEventsRequest.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.service.intelligence; +package android.service.contentcapture; parcelable ContentCaptureEventsRequest; diff --git a/core/java/android/service/intelligence/ContentCaptureEventsRequest.java b/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java index bc5b92bce999..df58f52dc59c 100644 --- a/core/java/android/service/intelligence/ContentCaptureEventsRequest.java +++ b/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.intelligence; +package android.service.contentcapture; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import android.view.intelligence.ContentCaptureEvent; +import android.view.contentcapture.ContentCaptureEvent; import java.util.List; diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java new file mode 100644 index 000000000000..3dfeeded5946 --- /dev/null +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2018 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.contentcapture; + +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.ComponentName; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import android.view.contentcapture.ContentCaptureEvent; + +import java.util.List; +import java.util.Set; + +/** + * A service used to capture the content of the screen to provide contextual data in other areas of + * the system such as Autofill. + * + * @hide + */ +@SystemApi +public abstract class ContentCaptureService extends Service { + + private static final String TAG = ContentCaptureService.class.getSimpleName(); + + // TODO(b/111330312): STOPSHIP use dynamic value, or change to false + static final boolean DEBUG = true; + static final boolean VERBOSE = false; + + /** + * The {@link Intent} that must be declared as handled by the service. + * + * <p>To be supported, the service must also require the + * {@link android.Manifest.permission#BIND_CONTENT_CAPTURE_SERVICE} permission so + * that other applications can not abuse it. + */ + public static final String SERVICE_INTERFACE = + "android.service.contentcapture.ContentCaptureService"; + + private Handler mHandler; + + private final IContentCaptureService mInterface = new IContentCaptureService.Stub() { + + @Override + public void onSessionLifecycle(InteractionContext context, String sessionId) + throws RemoteException { + if (context != null) { + mHandler.sendMessage( + obtainMessage(ContentCaptureService::handleOnCreateInteractionSession, + ContentCaptureService.this, context, sessionId)); + } else { + mHandler.sendMessage( + obtainMessage(ContentCaptureService::handleOnDestroyInteractionSession, + ContentCaptureService.this, sessionId)); + } + } + + @Override + public void onContentCaptureEventsRequest(String sessionId, + ContentCaptureEventsRequest request) { + mHandler.sendMessage( + obtainMessage(ContentCaptureService::handleOnContentCaptureEventsRequest, + ContentCaptureService.this, sessionId, request)); + + } + + @Override + public void onActivitySnapshot(String sessionId, SnapshotData snapshotData) { + mHandler.sendMessage( + obtainMessage(ContentCaptureService::handleOnActivitySnapshot, + ContentCaptureService.this, sessionId, snapshotData)); + } + }; + + @CallSuper + @Override + public void onCreate() { + super.onCreate(); + mHandler = new Handler(Looper.getMainLooper(), null, true); + } + + /** @hide */ + @Override + public final IBinder onBind(Intent intent) { + if (SERVICE_INTERFACE.equals(intent.getAction())) { + return mInterface.asBinder(); + } + Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent); + return null; + } + + /** + * Explicitly limits content capture to the given packages and activities. + * + * <p>When the whitelist is set, it overrides the values passed to + * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)} + * and {@link #setPackageContentCaptureEnabled(String, boolean)}. + * + * <p>To reset the whitelist, call it passing {@code null} to both arguments. + * + * <p>Useful when the service wants to restrict content capture to a category of apps, like + * chat apps. For example, if the service wants to support view captures on all activities of + * app {@code ChatApp1} and just activities {@code act1} and {@code act2} of {@code ChatApp2}, + * it would call: {@code setContentCaptureWhitelist(Arrays.asList("ChatApp1"), + * Arrays.asList(new ComponentName("ChatApp2", "act1"), + * new ComponentName("ChatApp2", "act2")));} + */ + public final void setContentCaptureWhitelist(@Nullable List<String> packages, + @Nullable List<ComponentName> activities) { + //TODO(b/111276913): implement + } + + /** + * Defines whether content capture should be enabled for activities with such + * {@link android.content.ComponentName}. + * + * <p>Useful to blacklist a particular activity. + */ + public final void setActivityContentCaptureEnabled(@NonNull ComponentName activity, + boolean enabled) { + //TODO(b/111276913): implement + } + + /** + * Defines whether content capture should be enabled for activities of the app with such + * {@code packageName}. + * + * <p>Useful to blacklist any activity from a particular app. + */ + public final void setPackageContentCaptureEnabled(@NonNull String packageName, + boolean enabled) { + //TODO(b/111276913): implement + } + + /** + * Gets the activities where content capture was disabled by + * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)}. + */ + @NonNull + public final Set<ComponentName> getContentCaptureDisabledActivities() { + //TODO(b/111276913): implement + return null; + } + + /** + * Gets the apps where content capture was disabled by + * {@link #setPackageContentCaptureEnabled(String, boolean)}. + */ + @NonNull + public final Set<String> getContentCaptureDisabledPackages() { + //TODO(b/111276913): implement + return null; + } + + /** + * Creates a new interaction session. + * + * @param context interaction context + * @param sessionId the session's Id + */ + public void onCreateInteractionSession(@NonNull InteractionContext context, + @NonNull InteractionSessionId sessionId) { + if (VERBOSE) { + Log.v(TAG, "onCreateInteractionSession(id=" + sessionId + ", ctx=" + context + ")"); + } + } + + /** + * Notifies the service of {@link ContentCaptureEvent events} associated with a content capture + * session. + * + * @param sessionId the session's Id + * @param request the events + */ + public abstract void onContentCaptureEventsRequest(@NonNull InteractionSessionId sessionId, + @NonNull ContentCaptureEventsRequest request); + + /** + * Notifies the service of {@link SnapshotData snapshot data} associated with a session. + * + * @param sessionId the session's Id + * @param snapshotData the data + */ + public void onActivitySnapshot(@NonNull InteractionSessionId sessionId, + @NonNull SnapshotData snapshotData) {} + + /** + * Destroys the interaction session. + * + * @param sessionId the id of the session to destroy + */ + public void onDestroyInteractionSession(@NonNull InteractionSessionId sessionId) { + if (VERBOSE) { + Log.v(TAG, "onDestroyInteractionSession(id=" + sessionId + ")"); + } + } + + //TODO(b/111276913): consider caching the InteractionSessionId for the lifetime of the session, + // so we don't need to create a temporary InteractionSessionId for each event. + + private void handleOnCreateInteractionSession(@NonNull InteractionContext context, + @NonNull String sessionId) { + onCreateInteractionSession(context, new InteractionSessionId(sessionId)); + } + + private void handleOnContentCaptureEventsRequest(@NonNull String sessionId, + @NonNull ContentCaptureEventsRequest request) { + onContentCaptureEventsRequest(new InteractionSessionId(sessionId), request); + } + + private void handleOnActivitySnapshot(@NonNull String sessionId, + @NonNull SnapshotData snapshotData) { + onActivitySnapshot(new InteractionSessionId(sessionId), snapshotData); + } + + private void handleOnDestroyInteractionSession(@NonNull String sessionId) { + onDestroyInteractionSession(new InteractionSessionId(sessionId)); + } +} diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl new file mode 100644 index 000000000000..29e9abbc6263 --- /dev/null +++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 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.contentcapture; + +import android.service.contentcapture.ContentCaptureEventsRequest; +import android.service.contentcapture.InteractionContext; +import android.service.contentcapture.SnapshotData; + +import java.util.List; + +/** + * Interface from the system to a Content Capture service. + * + * @hide + */ +oneway interface IContentCaptureService { + + // Called when session is created (context not null) or destroyed (context null) + void onSessionLifecycle(in InteractionContext context, String sessionId); + + void onContentCaptureEventsRequest(String sessionId, in ContentCaptureEventsRequest request); + + void onActivitySnapshot(String sessionId, in SnapshotData snapshotData); +} diff --git a/core/java/android/service/intelligence/InteractionContext.aidl b/core/java/android/service/contentcapture/InteractionContext.aidl index 4ce6aa45d4a1..982e095894fb 100644 --- a/core/java/android/service/intelligence/InteractionContext.aidl +++ b/core/java/android/service/contentcapture/InteractionContext.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.service.intelligence; +package android.service.contentcapture; parcelable InteractionContext; diff --git a/core/java/android/service/intelligence/InteractionContext.java b/core/java/android/service/contentcapture/InteractionContext.java index 7f4283db28d2..f1281ff01033 100644 --- a/core/java/android/service/intelligence/InteractionContext.java +++ b/core/java/android/service/contentcapture/InteractionContext.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.service.intelligence; +package android.service.contentcapture; import android.annotation.IntDef; import android.annotation.NonNull; @@ -37,7 +37,7 @@ public final class InteractionContext implements Parcelable { /** * Flag used to indicate that the app explicitly disabled content capture for the activity * (using - * {@link android.view.intelligence.ContentCaptureManager#setContentCaptureEnabled()}), + * {@link android.view.contentcapture.ContentCaptureManager#setContentCaptureEnabled(boolean)}), * in which case the service will just receive activity-level events. */ public static final int FLAG_DISABLED_BY_APP = 0x1; diff --git a/core/java/android/service/intelligence/InteractionSessionId.java b/core/java/android/service/contentcapture/InteractionSessionId.java index 667193b14113..84119472ca32 100644 --- a/core/java/android/service/intelligence/InteractionSessionId.java +++ b/core/java/android/service/contentcapture/InteractionSessionId.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.service.intelligence; +package android.service.contentcapture; import android.annotation.NonNull; import android.annotation.SystemApi; @@ -22,10 +22,12 @@ import android.os.Parcel; import android.os.Parcelable; import java.io.PrintWriter; -import java.util.UUID; -// TODO(b/111276913): add javadocs / implement equals/hashcode/string -/** @hide */ +/** + * Identifier for a Content Capture session. + * + * @hide + */ @SystemApi public final class InteractionSessionId implements Parcelable { @@ -34,15 +36,6 @@ public final class InteractionSessionId implements Parcelable { /** * Creates a new instance. * - * @hide - */ - public InteractionSessionId() { - this(UUID.randomUUID().toString()); - } - - /** - * Creates a new instance. - * * @param value The internal value. * * @hide diff --git a/core/java/android/service/intelligence/SnapshotData.aidl b/core/java/android/service/contentcapture/SnapshotData.aidl index 31d13390d2b8..a00e852625ee 100644 --- a/core/java/android/service/intelligence/SnapshotData.aidl +++ b/core/java/android/service/contentcapture/SnapshotData.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.service.intelligence; +package android.service.contentcapture; parcelable SnapshotData; diff --git a/core/java/android/service/intelligence/SnapshotData.java b/core/java/android/service/contentcapture/SnapshotData.java index b9310eae6cb5..bc2116a441aa 100644 --- a/core/java/android/service/intelligence/SnapshotData.java +++ b/core/java/android/service/contentcapture/SnapshotData.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.service.intelligence; +package android.service.contentcapture; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/service/intelligence/IIntelligenceService.aidl b/core/java/android/service/intelligence/IIntelligenceService.aidl deleted file mode 100644 index 2b924fbaadc4..000000000000 --- a/core/java/android/service/intelligence/IIntelligenceService.aidl +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2018 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.intelligence; - -import android.os.IBinder; -import android.service.intelligence.ContentCaptureEventsRequest; -import android.service.intelligence.InteractionSessionId; -import android.service.intelligence.InteractionContext; -import android.service.intelligence.SnapshotData; - -import android.view.autofill.AutofillId; -import android.view.autofill.AutofillValue; -import android.view.intelligence.ContentCaptureEvent; - -import java.util.List; - -/** - * Interface from the system to an intelligence service. - * - * @hide - */ - // TODO(b/111276913): rename / update javadoc (once the final name is defined) -oneway interface IIntelligenceService { - - // Called when session is created (context not null) or destroyed (context null) - void onSessionLifecycle(in InteractionContext context, in InteractionSessionId sessionId); - - void onContentCaptureEventsRequest(in InteractionSessionId sessionId, - in ContentCaptureEventsRequest request); - - void onActivitySnapshot(in InteractionSessionId sessionId, - in SnapshotData snapshotData); - - void onAutofillRequest(in InteractionSessionId sessionId, in IBinder autofillManagerClient, - int autofilSessionId, in AutofillId focusedId, - in AutofillValue focusedValue, long requestTime); - - void onDestroyAutofillWindowsRequest(in InteractionSessionId sessionId); -} diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java index 518f8ed11a26..de532b74375f 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -80,11 +80,18 @@ public final class Adjustment implements Parcelable { * Data type: int, one of importance values e.g. * {@link android.app.NotificationManager#IMPORTANCE_MIN}. * - * If used from - * {@link NotificationAssistantService#onNotificationEnqueued(StatusBarNotification)}, it can - * block a notification from appearing or silence it. If used from - * {@link NotificationAssistantService#adjustNotification(Adjustment)}, it can visually - * demote a notification. + * <p> If used from + * {@link NotificationAssistantService#onNotificationEnqueued(StatusBarNotification)}, and + * received before the notification is posted, it can block a notification from appearing or + * silence it. Importance adjustments received too late from + * {@link NotificationAssistantService#onNotificationEnqueued(StatusBarNotification)} will be + * ignored. + * </p> + * <p>If used from + * {@link NotificationAssistantService#adjustNotification(Adjustment)}, it can + * visually demote or cancel a notification, but use this with care if they notification was + * recently posted because the notification may already have made noise. + * </p> */ public static final String KEY_IMPORTANCE = "key_importance"; diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java index 5a7a83f19b0c..af7e93e0ed74 100644 --- a/core/java/android/service/notification/Condition.java +++ b/core/java/android/service/notification/Condition.java @@ -17,7 +17,6 @@ package android.service.notification; import android.annotation.IntDef; -import android.annotation.SystemApi; import android.content.Context; import android.net.Uri; import android.os.Parcel; @@ -35,7 +34,6 @@ import java.util.Objects; */ public final class Condition implements Parcelable { - @SystemApi public static final String SCHEME = "condition"; /** @hide */ @@ -59,14 +57,10 @@ public final class Condition implements Parcelable { */ public static final int STATE_TRUE = 1; - @SystemApi public static final int STATE_UNKNOWN = 2; - @SystemApi public static final int STATE_ERROR = 3; - @SystemApi public static final int FLAG_RELEVANT_NOW = 1 << 0; - @SystemApi public static final int FLAG_RELEVANT_ALWAYS = 1 << 1; /** @@ -81,9 +75,7 @@ public final class Condition implements Parcelable { */ public final String summary; - @SystemApi public final String line1; - @SystemApi public final String line2; /** @@ -94,9 +86,7 @@ public final class Condition implements Parcelable { @State public final int state; - @SystemApi public final int flags; - @SystemApi public final int icon; /** @@ -108,7 +98,6 @@ public final class Condition implements Parcelable { this(id, summary, "", "", -1, state, FLAG_RELEVANT_ALWAYS); } - @SystemApi public Condition(Uri id, String summary, String line1, String line2, int icon, int state, int flags) { if (id == null) throw new IllegalArgumentException("id is required"); @@ -177,7 +166,6 @@ public final class Condition implements Parcelable { proto.end(token); } - @SystemApi public static String stateToString(int state) { if (state == STATE_FALSE) return "STATE_FALSE"; if (state == STATE_TRUE) return "STATE_TRUE"; @@ -186,7 +174,6 @@ public final class Condition implements Parcelable { throw new IllegalArgumentException("state is invalid: " + state); } - @SystemApi public static String relevanceToString(int flags) { final boolean now = (flags & FLAG_RELEVANT_NOW) != 0; final boolean always = (flags & FLAG_RELEVANT_ALWAYS) != 0; @@ -219,7 +206,6 @@ public final class Condition implements Parcelable { return 0; } - @SystemApi public Condition copy() { final Parcel parcel = Parcel.obtain(); try { @@ -231,14 +217,12 @@ public final class Condition implements Parcelable { } } - @SystemApi public static Uri.Builder newId(Context context) { return new Uri.Builder() .scheme(Condition.SCHEME) .authority(context.getPackageName()); } - @SystemApi public static boolean isValidId(Uri id, String pkg) { return id != null && SCHEME.equals(id.getScheme()) && pkg.equals(id.getAuthority()); } diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java index 6fc689ab07cf..5203c8f4bb27 100644 --- a/core/java/android/service/notification/ConditionProviderService.java +++ b/core/java/android/service/notification/ConditionProviderService.java @@ -17,7 +17,6 @@ package android.service.notification; import android.annotation.SdkConstant; -import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.ActivityManager; import android.app.INotificationManager; @@ -107,7 +106,6 @@ public abstract class ConditionProviderService extends Service { */ abstract public void onConnected(); - @SystemApi public void onRequestConditions(int relevance) {} /** diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java index d7359f1aa5dd..3b813c7df82b 100644 --- a/core/java/android/service/textclassifier/TextClassifierService.java +++ b/core/java/android/service/textclassifier/TextClassifierService.java @@ -81,7 +81,6 @@ public abstract class TextClassifierService extends Service { * {@link android.Manifest.permission#BIND_TEXTCLASSIFIER_SERVICE} permission so * that other applications can not abuse it. */ - @SystemApi public static final String SERVICE_INTERFACE = "android.service.textclassifier.TextClassifierService"; @@ -407,9 +406,7 @@ public abstract class TextClassifierService extends Service { * Callbacks for TextClassifierService results. * * @param <T> the type of the result - * @hide */ - @SystemApi public interface Callback<T> { /** * Returns the result. diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index f9370a8aa6af..7e41878b3d4e 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -377,6 +377,9 @@ public class MeasuredParagraph { * @param start the inclusive start offset of the target region in the text * @param end the exclusive end offset of the target region in the text * @param textDir the text direction + * @param computeHyphenation true if need to compute hyphenation, otherwise false + * @param computeLayout true if need to compute full layout, otherwise false. + * @param hint pass if you already have measured paragraph. * @param recycle pass existing MeasuredParagraph if you want to recycle it. * * @return measured text @@ -389,12 +392,18 @@ public class MeasuredParagraph { @NonNull TextDirectionHeuristic textDir, boolean computeHyphenation, boolean computeLayout, + @Nullable MeasuredParagraph hint, @Nullable MeasuredParagraph recycle) { final MeasuredParagraph mt = recycle == null ? obtain() : recycle; mt.resetAndAnalyzeBidi(text, start, end, textDir); - final MeasuredText.Builder builder = new MeasuredText.Builder(mt.mCopiedBuffer); - builder.setComputeHyphenation(computeHyphenation); - builder.setComputeLayout(computeLayout); + final MeasuredText.Builder builder; + if (hint == null) { + builder = new MeasuredText.Builder(mt.mCopiedBuffer) + .setComputeHyphenation(computeHyphenation) + .setComputeLayout(computeLayout); + } else { + builder = new MeasuredText.Builder(hint.mMeasuredText); + } if (mt.mTextLength == 0) { // Need to build empty native measured text for StaticLayout. // TODO: Stop creating empty measured text for empty lines. diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java index b7ea0122cb06..08741d6a7d88 100644 --- a/core/java/android/text/PrecomputedText.java +++ b/core/java/android/text/PrecomputedText.java @@ -17,6 +17,7 @@ package android.text; import android.annotation.FloatRange; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -25,6 +26,8 @@ import android.text.style.MetricAffectingSpan; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; @@ -119,6 +122,16 @@ public class PrecomputedText implements Spannable { } /** + * Builder constructor from existing params. + */ + public Builder(@NonNull Params params) { + mPaint = params.mPaint; + mTextDir = params.mTextDir; + mBreakStrategy = params.mBreakStrategy; + mHyphenationFrequency = params.mHyphenationFrequency; + } + + /** * Set the line break strategy. * * The default value is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}. @@ -220,13 +233,41 @@ public class PrecomputedText implements Spannable { } /** @hide */ - public boolean isSameTextMetricsInternal(@NonNull TextPaint paint, + @IntDef(value = { UNUSABLE, NEED_RECOMPUTE, USABLE }) + @Retention(RetentionPolicy.SOURCE) + public @interface CheckResultUsableResult {} + + /** + * Constant for returning value of checkResultUsable indicating that given parameter is not + * compatible. + * @hide + */ + public static final int UNUSABLE = 0; + + /** + * Constant for returning value of checkResultUsable indicating that given parameter is not + * compatible but partially usable for creating new PrecomputedText. + * @hide + */ + public static final int NEED_RECOMPUTE = 1; + + /** + * Constant for returning value of checkResultUsable indicating that given parameter is + * compatible. + * @hide + */ + public static final int USABLE = 2; + + /** @hide */ + public @CheckResultUsableResult int checkResultUsable(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { - return mTextDir == textDir - && mBreakStrategy == strategy - && mHyphenationFrequency == frequency - && mPaint.equalsForTextMeasurement(paint); + if (mBreakStrategy == strategy && mHyphenationFrequency == frequency + && mPaint.equalsForTextMeasurement(paint)) { + return mTextDir == textDir ? USABLE : NEED_RECOMPUTE; + } else { + return UNUSABLE; + } } /** @@ -243,8 +284,8 @@ public class PrecomputedText implements Spannable { return false; } Params param = (Params) o; - return isSameTextMetricsInternal(param.mPaint, param.mTextDir, param.mBreakStrategy, - param.mHyphenationFrequency); + return checkResultUsable(param.mPaint, param.mTextDir, param.mBreakStrategy, + param.mHyphenationFrequency) == Params.USABLE; } @Override @@ -321,11 +362,55 @@ public class PrecomputedText implements Spannable { * @return A {@link PrecomputedText} */ public static PrecomputedText create(@NonNull CharSequence text, @NonNull Params params) { - ParagraphInfo[] paraInfo = createMeasuredParagraphs( - text, params, 0, text.length(), true /* computeLayout */); + ParagraphInfo[] paraInfo = null; + if (text instanceof PrecomputedText) { + final PrecomputedText hintPct = (PrecomputedText) text; + final PrecomputedText.Params hintParams = hintPct.getParams(); + final @Params.CheckResultUsableResult int checkResult = + hintParams.checkResultUsable(params.mPaint, params.mTextDir, + params.mBreakStrategy, params.mHyphenationFrequency); + switch (checkResult) { + case Params.USABLE: + return hintPct; + case Params.NEED_RECOMPUTE: + // To be able to use PrecomputedText for new params, at least break strategy and + // hyphenation frequency must be the same. + if (params.getBreakStrategy() == hintParams.getBreakStrategy() + && params.getHyphenationFrequency() + == hintParams.getHyphenationFrequency()) { + paraInfo = createMeasuredParagraphsFromPrecomputedText( + hintPct, params, true /* compute layout */); + } + break; + case Params.UNUSABLE: + // Unable to use anything in PrecomputedText. Create PrecomputedText as the + // normal text input. + } + + } + if (paraInfo == null) { + paraInfo = createMeasuredParagraphs( + text, params, 0, text.length(), true /* computeLayout */); + } return new PrecomputedText(text, 0, text.length(), params, paraInfo); } + private static ParagraphInfo[] createMeasuredParagraphsFromPrecomputedText( + @NonNull PrecomputedText pct, @NonNull Params params, boolean computeLayout) { + final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE + && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE; + ArrayList<ParagraphInfo> result = new ArrayList<>(); + for (int i = 0; i < pct.getParagraphCount(); ++i) { + final int paraStart = pct.getParagraphStart(i); + final int paraEnd = pct.getParagraphEnd(i); + result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( + params.getTextPaint(), pct, paraStart, paraEnd, params.getTextDirection(), + needHyphenation, computeLayout, pct.getMeasuredParagraph(i), + null /* no recycle */))); + } + return result.toArray(new ParagraphInfo[result.size()]); + } + /** @hide */ public static ParagraphInfo[] createMeasuredParagraphs( @NonNull CharSequence text, @NonNull Params params, @@ -350,7 +435,8 @@ public class PrecomputedText implements Spannable { result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( params.getTextPaint(), text, paraStart, paraEnd, params.getTextDirection(), - needHyphenation, computeLayout, null /* no recycle */))); + needHyphenation, computeLayout, null /* no hint */, + null /* no recycle */))); } return result.toArray(new ParagraphInfo[result.size()]); } @@ -434,12 +520,15 @@ public class PrecomputedText implements Spannable { * Returns true if the given TextPaint gives the same result of text layout for this text. * @hide */ - public boolean canUseMeasuredResult(@IntRange(from = 0) int start, @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, @NonNull TextPaint paint, - @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { - return mStart == start - && mEnd == end - && mParams.isSameTextMetricsInternal(paint, textDir, strategy, frequency); + public @Params.CheckResultUsableResult int checkResultUsable(@IntRange(from = 0) int start, + @IntRange(from = 0) int end, @NonNull TextDirectionHeuristic textDir, + @NonNull TextPaint paint, @Layout.BreakStrategy int strategy, + @Layout.HyphenationFrequency int frequency) { + if (mStart != start || mEnd != end) { + return Params.UNUSABLE; + } else { + return mParams.checkResultUsable(paint, textDir, strategy, frequency); + } } /** @hide */ diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 8cb18b255589..3d0c6622d8af 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -650,10 +650,26 @@ public class StaticLayout extends Layout { final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null; if (source instanceof PrecomputedText) { PrecomputedText precomputed = (PrecomputedText) source; - if (precomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint, - b.mBreakStrategy, b.mHyphenationFrequency)) { - // Some parameters are different from the ones when measured text is created. - paragraphInfo = precomputed.getParagraphInfo(); + final @PrecomputedText.Params.CheckResultUsableResult int checkResult = + precomputed.checkResultUsable(bufStart, bufEnd, textDir, paint, + b.mBreakStrategy, b.mHyphenationFrequency); + switch (checkResult) { + case PrecomputedText.Params.UNUSABLE: + break; + case PrecomputedText.Params.NEED_RECOMPUTE: + final PrecomputedText.Params newParams = + new PrecomputedText.Params.Builder(paint) + .setBreakStrategy(b.mBreakStrategy) + .setHyphenationFrequency(b.mHyphenationFrequency) + .setTextDirection(textDir) + .build(); + precomputed = PrecomputedText.create(precomputed, newParams); + paragraphInfo = precomputed.getParagraphInfo(); + break; + case PrecomputedText.Params.USABLE: + // Some parameters are different from the ones when measured text is created. + paragraphInfo = precomputed.getParagraphInfo(); + break; } } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index dee6d908c4c5..ade7577dc666 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -42,12 +42,15 @@ public class FeatureFlagUtils { static { DEFAULT_FLAGS = new HashMap<>(); DEFAULT_FLAGS.put("settings_audio_switcher", "true"); - DEFAULT_FLAGS.put("settings_systemui_theme", "true"); DEFAULT_FLAGS.put("settings_dynamic_homepage", "true"); DEFAULT_FLAGS.put("settings_mobile_network_v2", "true"); + DEFAULT_FLAGS.put("settings_network_and_internet_v2", "false"); DEFAULT_FLAGS.put("settings_seamless_transfer", "false"); + DEFAULT_FLAGS.put("settings_systemui_theme", "true"); + DEFAULT_FLAGS.put("settings_wifi_dpp", "false"); + DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "false"); + DEFAULT_FLAGS.put("settings_wifi_sharing", "false"); DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false"); - DEFAULT_FLAGS.put("settings_network_and_internet_v2", "false"); DEFAULT_FLAGS.put(SAFETY_HUB, "false"); DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false"); } diff --git a/core/java/android/view/InputEventCompatProcessor.java b/core/java/android/view/InputEventCompatProcessor.java new file mode 100644 index 000000000000..ff8407a40cf5 --- /dev/null +++ b/core/java/android/view/InputEventCompatProcessor.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 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.view; + +import android.content.Context; +import android.os.Build; + +import java.util.ArrayList; +import java.util.List; + +/** + * Compatibility processor for InputEvents that allows events to be adjusted before and + * after it is sent to the application. + * + * {@hide} + */ +public class InputEventCompatProcessor { + + protected Context mContext; + protected int mTargetSdkVersion; + + /** List of events to be used to return the processed events */ + private List<InputEvent> mProcessedEvents; + + public InputEventCompatProcessor(Context context) { + mContext = context; + mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; + mProcessedEvents = new ArrayList<>(); + } + + /** + * Processes the InputEvent for compatibility before it is sent to the app, allowing for the + * generation of more than one event if necessary. + * + * @param e The InputEvent to process + * @return The list of adjusted events, or null if no adjustments are needed. Do not keep a + * reference to the output as the list is reused. + */ + public List<InputEvent> processInputEventForCompatibility(InputEvent e) { + if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) { + mProcessedEvents.clear(); + MotionEvent motion = (MotionEvent) e; + final int mask = + MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY; + final int buttonState = motion.getButtonState(); + final int compatButtonState = (buttonState & mask) >> 4; + if (compatButtonState != 0) { + motion.setButtonState(buttonState | compatButtonState); + } + mProcessedEvents.add(motion); + return mProcessedEvents; + } + return null; + } + + /** + * Processes the InputEvent for compatibility before it is finished by calling + * InputEventReceiver#finishInputEvent(). + * + * @param e The InputEvent to process + * @return The InputEvent to finish, or null if it should not be finished + */ + public InputEvent processInputEventBeforeFinish(InputEvent e) { + // No changes needed + return e; + } +} diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index ba5340c826d3..fb4f9c03fa68 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -38,9 +38,14 @@ public class InsetsController implements WindowInsetsController { private final InsetsState mState = new InsetsState(); private final Rect mFrame = new Rect(); private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>(); + private final ViewRootImpl mViewRoot; private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>(); + public InsetsController(ViewRootImpl viewRoot) { + mViewRoot = viewRoot; + } + void onFrameChanged(Rect frame) { mFrame.set(frame); } @@ -49,8 +54,14 @@ public class InsetsController implements WindowInsetsController { return mState; } - public void setState(InsetsState state) { + boolean onStateChanged(InsetsState state) { + if (mState.equals(state)) { + return false; + } mState.set(state); + applyLocalVisibilityOverride(); + mViewRoot.notifyInsetsChanged(); + return true; } /** @@ -105,17 +116,28 @@ public class InsetsController implements WindowInsetsController { } } + private void applyLocalVisibilityOverride() { + for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { + final InsetsSourceConsumer controller = mSourceConsumers.valueAt(i); + controller.applyLocalVisibilityOverride(); + } + } + @VisibleForTesting public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetType int type) { InsetsSourceConsumer controller = mSourceConsumers.get(type); if (controller != null) { return controller; } - controller = new InsetsSourceConsumer(type, mState, Transaction::new); + controller = new InsetsSourceConsumer(type, mState, Transaction::new, this); mSourceConsumers.put(type, controller); return controller; } + void notifyVisibilityChanged() { + mViewRoot.notifyInsetsChanged(); + } + void dump(String prefix, PrintWriter pw) { pw.println(prefix); pw.println("InsetsController:"); mState.dump(prefix + " ", pw); diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index e74aa8dfcf4e..ec85c4c56cfc 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -33,27 +33,31 @@ public class InsetsSourceConsumer { private final Supplier<Transaction> mTransactionSupplier; private final @InternalInsetType int mType; private final InsetsState mState; - private @Nullable InsetsSourceControl mControl; + private final InsetsController mController; + private @Nullable InsetsSourceControl mSourceControl; private boolean mHidden; public InsetsSourceConsumer(@InternalInsetType int type, InsetsState state, - Supplier<Transaction> transactionSupplier) { + Supplier<Transaction> transactionSupplier, InsetsController controller) { mType = type; mState = state; mTransactionSupplier = transactionSupplier; + mController = controller; } public void setControl(@Nullable InsetsSourceControl control) { - if (mControl == control) { + if (mSourceControl == control) { return; } - mControl = control; + mSourceControl = control; applyHiddenToControl(); + applyLocalVisibilityOverride(); + mController.notifyVisibilityChanged(); } @VisibleForTesting public InsetsSourceControl getControl() { - return mControl; + return mSourceControl; } int getType() { @@ -70,25 +74,36 @@ public class InsetsSourceConsumer { setHidden(true); } + void applyLocalVisibilityOverride() { + + // If we don't have control, we are not able to change the visibility. + if (mSourceControl == null) { + return; + } + mState.getSource(mType).setVisible(!mHidden); + } + private void setHidden(boolean hidden) { if (mHidden == hidden) { return; } mHidden = hidden; applyHiddenToControl(); + applyLocalVisibilityOverride(); + mController.notifyVisibilityChanged(); } private void applyHiddenToControl() { - if (mControl == null) { + if (mSourceControl == null) { return; } // TODO: Animation final Transaction t = mTransactionSupplier.get(); if (mHidden) { - t.hide(mControl.getLeash()); + t.hide(mSourceControl.getLeash()); } else { - t.show(mControl.getLeash()); + t.show(mSourceControl.getLeash()); } t.apply(); } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index ab010855b896..a006e5de283e 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -36,6 +36,8 @@ import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.hardware.display.DisplayedContentSample; +import android.hardware.display.DisplayedContentSamplingAttributes; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -129,6 +131,12 @@ public class SurfaceControl implements Parcelable { int width, int height); private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs( IBinder displayToken); + private static native DisplayedContentSamplingAttributes + nativeGetDisplayedContentSamplingAttributes(IBinder displayToken); + private static native boolean nativeSetDisplayedContentSamplingEnabled(IBinder displayToken, + boolean enable, int componentMask, int maxFrames); + private static native DisplayedContentSample nativeGetDisplayedContentSample( + IBinder displayToken, long numFrames, long timestamp); private static native int nativeGetActiveConfig(IBinder displayToken); private static native boolean nativeSetActiveConfig(IBinder displayToken, int id); private static native int[] nativeGetDisplayColorModes(IBinder displayToken); @@ -1164,6 +1172,45 @@ public class SurfaceControl implements Parcelable { return nativeGetActiveConfig(displayToken); } + /** + * @hide + */ + public static DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes( + IBinder displayToken) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + return nativeGetDisplayedContentSamplingAttributes(displayToken); + } + + /** + * @hide + */ + public static boolean setDisplayedContentSamplingEnabled( + IBinder displayToken, boolean enable, int componentMask, int maxFrames) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + final int maxColorComponents = 4; + if ((componentMask >> maxColorComponents) != 0) { + throw new IllegalArgumentException("invalid componentMask when enabling sampling"); + } + return nativeSetDisplayedContentSamplingEnabled( + displayToken, enable, componentMask, maxFrames); + } + + /** + * @hide + */ + public static DisplayedContentSample getDisplayedContentSample( + IBinder displayToken, long maxFrames, long timestamp) { + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + return nativeGetDisplayedContentSample(displayToken, maxFrames, timestamp); + } + + public static boolean setActiveConfig(IBinder displayToken, int id) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index bf1a005bad18..34d076fba54d 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -299,6 +299,8 @@ public final class ThreadedRenderer extends HardwareRenderer { private boolean mEnabled; private boolean mRequested = true; + private FrameDrawingCallback mNextRtFrameCallback; + ThreadedRenderer(Context context, boolean translucent, String name) { super(); setName(name); @@ -432,6 +434,17 @@ public final class ThreadedRenderer extends HardwareRenderer { } /** + * Registers a callback to be executed when the next frame is being drawn on RenderThread. This + * callback will be executed on a RenderThread worker thread, and only used for the next frame + * and thus it will only fire once. + * + * @param callback The callback to register. + */ + void registerRtFrameCallback(FrameDrawingCallback callback) { + mNextRtFrameCallback = callback; + } + + /** * Destroys all hardware rendering resources associated with the specified * view hierarchy. * @@ -562,6 +575,15 @@ public final class ThreadedRenderer extends HardwareRenderer { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()"); updateViewTreeDisplayList(view); + // Consume and set the frame callback after we dispatch draw to the view above, but before + // onPostDraw below which may reset the callback for the next frame. This ensures that + // updates to the frame callback during scroll handling will also apply in this frame. + final FrameDrawingCallback callback = mNextRtFrameCallback; + mNextRtFrameCallback = null; + if (callback != null) { + setFrameCallback(callback); + } + if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) { RecordingCanvas canvas = mRootNode.startRecording(mSurfaceWidth, mSurfaceHeight); try { @@ -619,10 +641,8 @@ public final class ThreadedRenderer extends HardwareRenderer { * * @param view The view to draw. * @param attachInfo AttachInfo tied to the specified view. - * @param callbacks Callbacks invoked when drawing happens. */ - void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks, - FrameDrawingCallback frameDrawingCallback) { + void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) { final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer; choreographer.mFrameInfo.markDrawStart(); @@ -642,9 +662,6 @@ public final class ThreadedRenderer extends HardwareRenderer { attachInfo.mPendingAnimatingRenderNodes = null; } - if (frameDrawingCallback != null) { - setFrameCallback(frameDrawingCallback); - } int syncResult = syncAndDrawFrame(choreographer.mFrameInfo); if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) { setEnabled(false); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index bd2aa64b33a1..4297efb71ad0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -109,13 +109,13 @@ import android.view.animation.Transformation; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; +import android.view.contentcapture.ContentCaptureManager; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.inspector.InspectableProperty; import android.view.inspector.InspectableProperty.EnumMap; import android.view.inspector.InspectableProperty.FlagMap; -import android.view.intelligence.ContentCaptureManager; import android.widget.Checkable; import android.widget.FrameLayout; import android.widget.ScrollBarDrawable; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index cb4788624935..9fe0ddc110c2 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -124,6 +124,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; +import java.util.List; import java.util.Queue; import java.util.concurrent.CountDownLatch; @@ -200,8 +201,6 @@ public final class ViewRootImpl implements ViewParent, static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList(); static boolean sFirstDrawComplete = false; - private FrameDrawingCallback mNextRtFrameCallback; - /** * Callback for notifying about global configuration changes. */ @@ -464,7 +463,7 @@ public final class ViewRootImpl implements ViewParent, final DisplayCutout.ParcelableWrapper mPendingDisplayCutout = new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); boolean mPendingAlwaysConsumeNavBar; - private InsetsState mPendingInsets = new InsetsState(); + private InsetsState mTempInsets = new InsetsState(); final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets = new ViewTreeObserver.InternalInsetsInfo(); @@ -545,6 +544,8 @@ public final class ViewRootImpl implements ViewParent, private boolean mNeedsRendererSetup; + private final InputEventCompatProcessor mInputCompatProcessor; + /** * Consistency verifier for debugging purposes. */ @@ -552,7 +553,7 @@ public final class ViewRootImpl implements ViewParent, InputEventConsistencyVerifier.isInstrumentationEnabled() ? new InputEventConsistencyVerifier(this, 0) : null; - private final InsetsController mInsetsController = new InsetsController(); + private final InsetsController mInsetsController = new InsetsController(this); static final class SystemUiVisibilityInfo { int seq; @@ -600,6 +601,25 @@ public final class ViewRootImpl implements ViewParent, mChoreographer = Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); + String processorOverrideName = context.getResources().getString( + R.string.config_inputEventCompatProcessorOverrideClassName); + if (processorOverrideName.isEmpty()) { + // No compatibility processor override, using default. + mInputCompatProcessor = new InputEventCompatProcessor(context); + } else { + InputEventCompatProcessor compatProcessor = null; + try { + final Class<? extends InputEventCompatProcessor> klass = + (Class<? extends InputEventCompatProcessor>) Class.forName( + processorOverrideName); + compatProcessor = klass.getConstructor(Context.class).newInstance(context); + } catch (Exception e) { + Log.e(TAG, "Unable to create the InputEventCompatProcessor. ", e); + } finally { + mInputCompatProcessor = compatProcessor; + } + } + if (!sCompatibilityDone) { sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P; @@ -823,7 +843,7 @@ public final class ViewRootImpl implements ViewParent, getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, - mInsetsController.getState()); + mTempInsets); setFrame(mTmpFrame); } catch (RemoteException e) { mAdded = false; @@ -851,7 +871,7 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mAlwaysConsumeNavBar = (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR) != 0; mPendingAlwaysConsumeNavBar = mAttachInfo.mAlwaysConsumeNavBar; - mPendingInsets = mInsetsController.getState(); + mInsetsController.onStateChanged(mTempInsets); if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow); if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; @@ -1052,7 +1072,9 @@ public final class ViewRootImpl implements ViewParent, * @param callback The callback to register. */ public void registerRtFrameCallback(FrameDrawingCallback callback) { - mNextRtFrameCallback = callback; + if (mAttachInfo.mThreadedRenderer != null) { + mAttachInfo.mThreadedRenderer.registerRtFrameCallback(callback); + } } @UnsupportedAppUsage @@ -1342,6 +1364,19 @@ public final class ViewRootImpl implements ViewParent, scheduleTraversals(); } + void notifyInsetsChanged() { + if (!USE_NEW_INSETS) { + return; + } + mApplyInsetsRequested = true; + + // If this changes during traversal, no need to schedule another one as it will dispatch it + // during the current traversal. + if (!mIsInTraversal) { + scheduleTraversals(); + } + } + @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { @@ -2027,9 +2062,6 @@ public final class ViewRootImpl implements ViewParent, if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) { insetsChanged = true; } - if (!mPendingInsets.equals(mInsetsController.getState())) { - insetsChanged = true; - } if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { windowSizeMayChange = true; @@ -2223,8 +2255,6 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mStableInsets); final boolean cutoutChanged = !mPendingDisplayCutout.equals( mAttachInfo.mDisplayCutout); - final boolean insetsStateChanged = !mPendingInsets.equals( - mInsetsController.getState()); final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets); final boolean surfaceSizeChanged = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0; @@ -2262,10 +2292,6 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mAlwaysConsumeNavBar = mPendingAlwaysConsumeNavBar; contentInsetsChanged = true; } - if (insetsStateChanged) { - mInsetsController.setState(mPendingInsets); - contentInsetsChanged = true; - } if (contentInsetsChanged || mLastSystemUiVisibility != mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested || mLastOverscanRequested != mAttachInfo.mOverscanRequested @@ -3534,10 +3560,7 @@ public final class ViewRootImpl implements ViewParent, useAsyncReport = true; - // draw(...) might invoke post-draw, which might register the next callback already. - final FrameDrawingCallback callback = mNextRtFrameCallback; - mNextRtFrameCallback = null; - mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback); + mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); } else { // If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface @@ -4378,22 +4401,12 @@ public final class ViewRootImpl implements ViewParent, } break; case MSG_INSETS_CHANGED: - mPendingInsets = (InsetsState) msg.obj; - - // TODO: Full traversal not needed here. - if (USE_NEW_INSETS) { - requestLayout(); - } + mInsetsController.onStateChanged((InsetsState) msg.obj); break; case MSG_INSETS_CONTROL_CHANGED: { SomeArgs args = (SomeArgs) msg.obj; - mPendingInsets = (InsetsState) args.arg1; mInsetsController.onControlsChanged((InsetsSourceControl[]) args.arg2); - - // TODO: Full traversal not necessarily needed here. - if (USE_NEW_INSETS) { - requestLayout(); - } + mInsetsController.onStateChanged((InsetsState) args.arg1); break; } case MSG_WINDOW_MOVED: @@ -6794,7 +6807,7 @@ public final class ViewRootImpl implements ViewParent, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, mTmpFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout, - mPendingMergedConfiguration, mSurface, mPendingInsets); + mPendingMergedConfiguration, mSurface, mTempInsets); mPendingAlwaysConsumeNavBar = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0; @@ -6811,7 +6824,7 @@ public final class ViewRootImpl implements ViewParent, mTranslator.translateRectInScreenToAppWindow(mPendingStableInsets); } setFrame(mTmpFrame); - + mInsetsController.onStateChanged(mTempInsets); return relayoutResult; } @@ -7175,6 +7188,7 @@ public final class ViewRootImpl implements ViewParent, public static final int FLAG_FINISHED_HANDLED = 1 << 3; public static final int FLAG_RESYNTHESIZED = 1 << 4; public static final int FLAG_UNHANDLED = 1 << 5; + public static final int FLAG_MODIFIED_FOR_COMPATIBILITY = 1 << 6; public QueuedInputEvent mNext; @@ -7267,7 +7281,6 @@ public final class ViewRootImpl implements ViewParent, @UnsupportedAppUsage void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) { - adjustInputEventForCompatibility(event); QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); // Always enqueue the input event in order, regardless of its time stamp. @@ -7370,7 +7383,22 @@ public final class ViewRootImpl implements ViewParent, if (q.mReceiver != null) { boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0; - q.mReceiver.finishInputEvent(q.mEvent, handled); + boolean modified = (q.mFlags & QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY) != 0; + if (modified) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventBeforeFinish"); + InputEvent processedEvent; + try { + processedEvent = + mInputCompatProcessor.processInputEventBeforeFinish(q.mEvent); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + if (processedEvent != null) { + q.mReceiver.finishInputEvent(processedEvent, handled); + } + } else { + q.mReceiver.finishInputEvent(q.mEvent, handled); + } } else { q.mEvent.recycleIfNeededAfterDispatch(); } @@ -7378,19 +7406,6 @@ public final class ViewRootImpl implements ViewParent, recycleQueuedInputEvent(q); } - private void adjustInputEventForCompatibility(InputEvent e) { - if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) { - MotionEvent motion = (MotionEvent) e; - final int mask = - MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY; - final int buttonState = motion.getButtonState(); - final int compatButtonState = (buttonState & mask) >> 4; - if (compatButtonState != 0) { - motion.setButtonState(buttonState | compatButtonState); - } - } - } - static boolean isTerminalInputEvent(InputEvent event) { if (event instanceof KeyEvent) { final KeyEvent keyEvent = (KeyEvent)event; @@ -7461,7 +7476,28 @@ public final class ViewRootImpl implements ViewParent, @Override public void onInputEvent(InputEvent event) { - enqueueInputEvent(event, this, 0, true); + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility"); + List<InputEvent> processedEvents; + try { + processedEvents = + mInputCompatProcessor.processInputEventForCompatibility(event); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + if (processedEvents != null) { + if (processedEvents.isEmpty()) { + // InputEvent consumed by mInputCompatProcessor + finishInputEvent(event, true); + } else { + for (int i = 0; i < processedEvents.size(); i++) { + enqueueInputEvent( + processedEvents.get(i), this, + QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true); + } + } + } else { + enqueueInputEvent(event, this, 0, true); + } } @Override diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index f7c9a0bf0f4b..763ce4f45b01 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -18,7 +18,6 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.Rect; @@ -749,7 +748,6 @@ public final class ViewTreeObserver { * * @param callback The callback to invoke when the frame is committed. */ - @TestApi public void registerFrameCommitCallback(@NonNull Runnable callback) { checkIsAlive(); if (mOnFrameCommitListeners == null) { @@ -772,7 +770,6 @@ public final class ViewTreeObserver { * not be invoked. If false is returned then the callback was either never added * or may already be pending execution and was unable to be removed */ - @TestApi public boolean unregisterFrameCommitCallback(@NonNull Runnable callback) { checkIsAlive(); if (mOnFrameCommitListeners == null) { diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 88b9c8096fe1..c5c1bcae232a 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1004,6 +1004,36 @@ public final class AccessibilityManager { } /** + * Returns accessibility window id from window token. Accessibility window id is the one + * returned from AccessibilityWindowInfo.getId(). Only available for the system process. + * + * @param windowToken Window token to find accessibility window id. + * @return Accessibility window id for the window token. + * AccessibilityWindowInfo.UNDEFINED_WINDOW_ID if accessibility window id not available for + * the token. + * @hide + */ + @SystemApi + public int getAccessibilityWindowId(IBinder windowToken) { + if (windowToken == null) { + return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + } + + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + } + } + try { + return service.getAccessibilityWindowId(windowToken); + } catch (RemoteException e) { + return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + } + } + + /** * Sets the current state and notifies listeners, if necessary. * * @param stateFlags The state flags. diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 2767a82e5dba..38dac94340bb 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -76,5 +76,8 @@ interface IAccessibilityManager { // System process only boolean sendFingerprintGesture(int gestureKeyCode); + // System process only + int getAccessibilityWindowId(IBinder windowToken); + long getRecommendedTimeoutMillis(); } diff --git a/core/java/android/view/intelligence/ContentCaptureEvent.aidl b/core/java/android/view/contentcapture/ContentCaptureEvent.aidl index c66a6cb0d486..abaf9ed7d7e9 100644 --- a/core/java/android/view/intelligence/ContentCaptureEvent.aidl +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.view.intelligence; +package android.view.contentcapture; parcelable ContentCaptureEvent; diff --git a/core/java/android/view/intelligence/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java index f63628105cba..66fa530758e7 100644 --- a/core/java/android/view/intelligence/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.view.intelligence; +package android.view.contentcapture; import android.annotation.IntDef; import android.annotation.NonNull; @@ -42,22 +42,38 @@ public final class ContentCaptureEvent implements Parcelable { /** * Called when the activity is started. + * + * @deprecated - TODO(b/111276913): we should abstract the Activity lifecycle concepts into + * something related to a session and/or domain. */ + @Deprecated public static final int TYPE_ACTIVITY_STARTED = 1; /** * Called when the activity is resumed. + * + * @deprecated - TODO(b/111276913): we should abstract the Activity lifecycle concepts into + * something related to a session and/or domain. */ + @Deprecated public static final int TYPE_ACTIVITY_RESUMED = 2; /** * Called when the activity is paused. + * + * @deprecated - TODO(b/111276913): we should abstract the Activity lifecycle concepts into + * something related to a session and/or domain. */ + @Deprecated public static final int TYPE_ACTIVITY_PAUSED = 3; /** * Called when the activity is stopped. + * + * @deprecated - TODO(b/111276913): we should abstract the Activity lifecycle concepts into + * something related to a session and/or domain. */ + @Deprecated public static final int TYPE_ACTIVITY_STOPPED = 4; /** @@ -163,7 +179,7 @@ public final class ContentCaptureEvent implements Parcelable { * Gets optional flags associated with the event. * * @return either {@code 0} or - * {@link android.view.intelligence.ContentCaptureManager#FLAG_USER_INPUT}. + * {@link android.view.contentcapture.ContentCaptureManager#FLAG_USER_INPUT}. */ public int getFlags() { return mFlags; diff --git a/core/java/android/view/intelligence/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 45518d5e5943..1889692ea831 100644 --- a/core/java/android/view/intelligence/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.view.intelligence; +package android.view.contentcapture; -import static android.view.intelligence.ContentCaptureEvent.TYPE_VIEW_APPEARED; -import static android.view.intelligence.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED; -import static android.view.intelligence.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -31,18 +31,18 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; -import android.service.intelligence.InteractionSessionId; import android.util.Log; import android.view.View; import android.view.ViewStructure; import android.view.autofill.AutofillId; -import android.view.intelligence.ContentCaptureEvent.EventType; +import android.view.contentcapture.ContentCaptureEvent.EventType; import com.android.internal.os.IResultReceiver; import com.android.internal.util.Preconditions; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; /* @@ -58,7 +58,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE) public final class ContentCaptureManager { - private static final String TAG = "ContentCaptureManager"; + private static final String TAG = ContentCaptureManager.class.getSimpleName(); // TODO(b/111276913): define a way to dynamically set them(for example, using settings?) private static final boolean VERBOSE = false; @@ -113,10 +113,10 @@ public final class ContentCaptureManager { private final Context mContext; @Nullable - private final IIntelligenceManager mService; + private final IContentCaptureManager mService; @Nullable - private InteractionSessionId mId; + private String mId; private int mState = STATE_UNKNOWN; @@ -137,7 +137,8 @@ public final class ContentCaptureManager { private final Handler mHandler; /** @hide */ - public ContentCaptureManager(@NonNull Context context, @Nullable IIntelligenceManager service) { + public ContentCaptureManager(@NonNull Context context, + @Nullable IContentCaptureManager service) { mContext = Preconditions.checkNotNull(context, "context cannot be null"); if (VERBOSE) { Log.v(TAG, "Constructor for " + context.getPackageName()); @@ -165,7 +166,7 @@ public final class ContentCaptureManager { return; } mState = STATE_WAITING_FOR_SERVER; - mId = new InteractionSessionId(); + mId = UUID.randomUUID().toString(); mApplicationToken = token; mComponentName = componentName; @@ -421,7 +422,7 @@ public final class ContentCaptureManager { /** @hide */ public void dump(String prefix, PrintWriter pw) { - pw.print(prefix); pw.println("IntelligenceManager"); + pw.print(prefix); pw.println("ContentCaptureManager"); final String prefix2 = prefix + " "; pw.print(prefix2); pw.print("mContext: "); pw.println(mContext); pw.print(prefix2); pw.print("user: "); pw.println(mContext.getUserId()); diff --git a/core/java/android/view/intelligence/IIntelligenceManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl index 882fb2674bf1..8704dad2ea08 100644 --- a/core/java/android/view/intelligence/IIntelligenceManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl @@ -14,12 +14,11 @@ * limitations under the License. */ -package android.view.intelligence; +package android.view.contentcapture; import android.content.ComponentName; +import android.view.contentcapture.ContentCaptureEvent; import android.os.IBinder; -import android.service.intelligence.InteractionSessionId; -import android.view.intelligence.ContentCaptureEvent; import com.android.internal.os.IResultReceiver; @@ -28,23 +27,9 @@ import java.util.List; /** * {@hide} */ -// TODO(b/111276913): rename once the final name is defined -oneway interface IIntelligenceManager { - /** - * Starts a session, sending the "remote" sessionId to the receiver. - */ +oneway interface IContentCaptureManager { void startSession(int userId, IBinder activityToken, in ComponentName componentName, - in InteractionSessionId sessionId, int flags, in IResultReceiver result); - - /** - * Finishes a session. - */ - void finishSession(int userId, in InteractionSessionId sessionId, - in List<ContentCaptureEvent> events); - - /** - * Sends a batch of events - */ - void sendEvents(int userId, in InteractionSessionId sessionId, - in List<ContentCaptureEvent> events); + String sessionId, int flags, in IResultReceiver result); + void finishSession(int userId, String sessionId, in List<ContentCaptureEvent> events); + void sendEvents(int userId, in String sessionId, in List<ContentCaptureEvent> events); } diff --git a/core/java/android/view/intelligence/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java index ea57461f0933..86b89adb5ce3 100644 --- a/core/java/android/view/intelligence/ViewNode.java +++ b/core/java/android/view/contentcapture/ViewNode.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.view.intelligence; +package android.view.contentcapture; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 13409554da28..e57fdffdfd1a 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2354,17 +2354,19 @@ public final class InputMethodManager { } /** - * Shows the input method chooser dialog. + * Shows the input method chooser dialog from system. * * @param showAuxiliarySubtypes Set true to show auxiliary input methods. + * @param displayId The ID of the display where the chooser dialog should be shown. * @hide */ - public void showInputMethodPicker(boolean showAuxiliarySubtypes) { + @RequiresPermission(WRITE_SECURE_SETTINGS) + public void showInputMethodPickerFromSystem(boolean showAuxiliarySubtypes, int displayId) { final int mode = showAuxiliarySubtypes ? SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES : SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES; try { - mService.showInputMethodPickerFromClient(mClient, mode); + mService.showInputMethodPickerFromSystem(mClient, mode, displayId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2520,16 +2522,6 @@ public final class InputMethodManager { */ @Deprecated public boolean switchToLastInputMethod(IBinder imeToken) { - if (imeToken == null) { - // Note: null token is allowed for callers that have WRITE_SECURE_SETTINGS permission. - // Thus we cannot always rely on InputMethodPrivilegedOperationsRegistry unfortunately. - // TODO(Bug 114488811): Consider deprecating null token rule. - try { - return mService.switchToPreviousInputMethod(imeToken); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } return InputMethodPrivilegedOperationsRegistry.get(imeToken).switchToPreviousInputMethod(); } @@ -2548,16 +2540,6 @@ public final class InputMethodManager { */ @Deprecated public boolean switchToNextInputMethod(IBinder imeToken, boolean onlyCurrentIme) { - if (imeToken == null) { - // Note: null token is allowed for callers that have WRITE_SECURE_SETTINGS permission. - // Thus we cannot always rely on InputMethodPrivilegedOperationsRegistry unfortunately. - // TODO(Bug 114488811): Consider deprecating null token rule. - try { - return mService.switchToNextInputMethod(imeToken, onlyCurrentIme); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } return InputMethodPrivilegedOperationsRegistry.get(imeToken) .switchToNextInputMethod(onlyCurrentIme); } diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java index 797b861e9e70..b41096c74bf7 100644 --- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java +++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java @@ -16,7 +16,6 @@ package android.view.textclassifier; -import android.annotation.NonNull; import android.app.Person; import android.text.TextUtils; import android.util.ArrayMap; @@ -30,6 +29,7 @@ import java.util.ArrayList; import java.util.Deque; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -57,9 +57,9 @@ public final class ActionsSuggestionsHelper { * </ul> * User A will be encoded as 2, user B will be encoded as 1 and local user will be encoded as 0. */ - @NonNull public static ActionsSuggestionsModel.ConversationMessage[] toNativeMessages( - @NonNull List<ConversationActions.Message> messages) { + List<ConversationActions.Message> messages, + Function<CharSequence, String> languageDetector) { List<ConversationActions.Message> messagesWithText = messages.stream() .filter(message -> !TextUtils.isEmpty(message.getText())) @@ -67,31 +67,18 @@ public final class ActionsSuggestionsHelper { if (messagesWithText.isEmpty()) { return new ActionsSuggestionsModel.ConversationMessage[0]; } - int size = messagesWithText.size(); - // If the last message (the most important one) does not have the Person object, we will - // just use the last message and consider this message is sent from a remote user. - ConversationActions.Message lastMessage = messages.get(size - 1); - boolean useLastMessageOnly = lastMessage.getAuthor() == null; - if (useLastMessageOnly) { - return new ActionsSuggestionsModel.ConversationMessage[]{ - new ActionsSuggestionsModel.ConversationMessage( - FIRST_NON_LOCAL_USER, - lastMessage.getText().toString(), - 0, - null)}; - } - - // Encode the messages in the reverse order, stop whenever the Person object is missing. Deque<ActionsSuggestionsModel.ConversationMessage> nativeMessages = new ArrayDeque<>(); PersonEncoder personEncoder = new PersonEncoder(); + int size = messagesWithText.size(); for (int i = size - 1; i >= 0; i--) { ConversationActions.Message message = messagesWithText.get(i); - if (message.getAuthor() == null) { - break; - } + long referenceTime = message.getReferenceTime() == null + ? 0 + : message.getReferenceTime().toInstant().toEpochMilli(); nativeMessages.push(new ActionsSuggestionsModel.ConversationMessage( personEncoder.encode(message.getAuthor()), - message.getText().toString(), 0, null)); + message.getText().toString(), referenceTime, + languageDetector.apply(message.getText()))); } return nativeMessages.toArray( new ActionsSuggestionsModel.ConversationMessage[nativeMessages.size()]); diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index 1a7b91127a7b..04924c9a59c4 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -30,6 +30,7 @@ import android.os.Parcelable; import android.text.SpannedString; import android.util.ArraySet; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; @@ -348,17 +349,31 @@ public final class ConversationActions implements Parcelable { /** * Represents the local user. * - * @see Builder#setAuthor(Person) + * @see Builder#Builder(Person) */ public static final Person PERSON_USER_LOCAL = new Person.Builder() .setKey("text-classifier-conversation-actions-local-user") .build(); + /** + * Represents the remote user. + * <p> + * If possible, you are suggested to create a {@link Person} object that can identify + * the remote user better, so that the underlying model could differentiate between + * different remote users. + * + * @see Builder#Builder(Person) + */ + public static final Person PERSON_USER_REMOTE = + new Person.Builder() + .setKey("text-classifier-conversation-actions-remote-user") + .build(); + @Nullable private final Person mAuthor; @Nullable - private final ZonedDateTime mComposeTime; + private final ZonedDateTime mReferenceTime; @Nullable private final CharSequence mText; @NonNull @@ -366,18 +381,18 @@ public final class ConversationActions implements Parcelable { private Message( @Nullable Person author, - @Nullable ZonedDateTime composeTime, + @Nullable ZonedDateTime referenceTime, @Nullable CharSequence text, @NonNull Bundle bundle) { mAuthor = author; - mComposeTime = composeTime; + mReferenceTime = referenceTime; mText = text; mExtras = Preconditions.checkNotNull(bundle); } private Message(Parcel in) { mAuthor = in.readParcelable(null); - mComposeTime = + mReferenceTime = in.readInt() == 0 ? null : ZonedDateTime.parse( @@ -389,9 +404,9 @@ public final class ConversationActions implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeParcelable(mAuthor, flags); - parcel.writeInt(mComposeTime != null ? 1 : 0); - if (mComposeTime != null) { - parcel.writeString(mComposeTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); + parcel.writeInt(mReferenceTime != null ? 1 : 0); + if (mReferenceTime != null) { + parcel.writeString(mReferenceTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); } parcel.writeCharSequence(mText); parcel.writeBundle(mExtras); @@ -416,15 +431,18 @@ public final class ConversationActions implements Parcelable { }; /** Returns the person that composed the message. */ - @Nullable + @NonNull public Person getAuthor() { return mAuthor; } - /** Returns the compose time of the message. */ + /** + * Returns the reference time of the message, for example it could be the compose or send + * time of this message. + */ @Nullable - public ZonedDateTime getTime() { - return mComposeTime; + public ZonedDateTime getReferenceTime() { + return mReferenceTime; } /** Returns the text of the message. */ @@ -450,34 +468,38 @@ public final class ConversationActions implements Parcelable { @Nullable private Person mAuthor; @Nullable - private ZonedDateTime mComposeTime; + private ZonedDateTime mReferenceTime; @Nullable private CharSequence mText; @Nullable private Bundle mExtras; /** - * Sets the person who composed this message. - * <p> - * Use {@link #PERSON_USER_LOCAL} to represent the local user. + * Constructs a builder. + * + * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL} + * to represent the local user. If it is not possible to identify the + * remote user that the local user is conversing with, use + * {@link #PERSON_USER_REMOTE} to represent a remote user. */ - @NonNull - public Builder setAuthor(@Nullable Person author) { - mAuthor = author; - return this; + public Builder(@NonNull Person author) { + mAuthor = Preconditions.checkNotNull(author); } - /** Sets the text of this message */ + /** Sets the text of this message. */ @NonNull public Builder setText(@Nullable CharSequence text) { mText = text; return this; } - /** Sets the compose time of this message */ + /** + * Sets the reference time of this message, for example it could be the compose or send + * time of this message. + */ @NonNull - public Builder setComposeTime(@Nullable ZonedDateTime composeTime) { - mComposeTime = composeTime; + public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) { + mReferenceTime = referenceTime; return this; } @@ -493,7 +515,7 @@ public final class ConversationActions implements Parcelable { public Message build() { return new Message( mAuthor, - mComposeTime, + mReferenceTime, mText == null ? null : new SpannedString(mText), mExtras == null ? new Bundle() : mExtras.deepCopy()); } @@ -654,6 +676,8 @@ public final class ConversationActions implements Parcelable { @NonNull @Hint private final List<String> mHints; + @Nullable + private String mCallingPackageName; private Request( @NonNull List<Message> conversation, @@ -666,15 +690,26 @@ public final class ConversationActions implements Parcelable { mHints = hints; } - private Request(Parcel in) { + private static Request readFromParcel(Parcel in) { List<Message> conversation = new ArrayList<>(); in.readParcelableList(conversation, null); - mConversation = Collections.unmodifiableList(conversation); - mTypeConfig = in.readParcelable(null); - mMaxSuggestions = in.readInt(); + + TypeConfig typeConfig = in.readParcelable(null); + + int maxSuggestions = in.readInt(); + List<String> hints = new ArrayList<>(); in.readStringList(hints); - mHints = Collections.unmodifiableList(hints); + + String callingPackageName = in.readString(); + + Request request = new Request( + conversation, + typeConfig, + maxSuggestions, + hints); + request.setCallingPackageName(callingPackageName); + return request; } @Override @@ -683,6 +718,7 @@ public final class ConversationActions implements Parcelable { parcel.writeParcelable(mTypeConfig, flags); parcel.writeInt(mMaxSuggestions); parcel.writeStringList(mHints); + parcel.writeString(mCallingPackageName); } @Override @@ -694,7 +730,7 @@ public final class ConversationActions implements Parcelable { new Creator<Request>() { @Override public Request createFromParcel(Parcel in) { - return new Request(in); + return readFromParcel(in); } @Override @@ -730,6 +766,26 @@ public final class ConversationActions implements Parcelable { return mHints; } + /** + * Sets the name of the package that is sending this request. + * <p> + * Package-private for SystemTextClassifier's use. + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setCallingPackageName(@Nullable String callingPackageName) { + mCallingPackageName = callingPackageName; + } + + /** + * Returns the name of the package that sent this request. + * This returns {@code null} if no calling package name is set. + */ + @Nullable + public String getCallingPackageName() { + return mCallingPackageName; + } + /** Builder object to construct the {@link Request} object. */ public static final class Builder { @NonNull diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java index f8fce62c84d2..c24489c8740b 100644 --- a/core/java/android/view/textclassifier/SystemTextClassifier.java +++ b/core/java/android/view/textclassifier/SystemTextClassifier.java @@ -60,7 +60,7 @@ public final class SystemTextClassifier implements TextClassifier { mSettings = Preconditions.checkNotNull(settings); mFallback = context.getSystemService(TextClassificationManager.class) .getTextClassifier(TextClassifier.LOCAL); - mPackageName = Preconditions.checkNotNull(context.getPackageName()); + mPackageName = Preconditions.checkNotNull(context.getOpPackageName()); } /** @@ -72,6 +72,7 @@ public final class SystemTextClassifier implements TextClassifier { Preconditions.checkNotNull(request); Utils.checkMainThread(); try { + request.setCallingPackageName(mPackageName); final TextSelectionCallback callback = new TextSelectionCallback(); mManagerService.onSuggestSelection(mSessionId, request, callback); final TextSelection selection = callback.mReceiver.get(); @@ -93,6 +94,7 @@ public final class SystemTextClassifier implements TextClassifier { Preconditions.checkNotNull(request); Utils.checkMainThread(); try { + request.setCallingPackageName(mPackageName); final TextClassificationCallback callback = new TextClassificationCallback(); mManagerService.onClassifyText(mSessionId, request, callback); final TextClassification classification = callback.mReceiver.get(); @@ -150,6 +152,7 @@ public final class SystemTextClassifier implements TextClassifier { Utils.checkMainThread(); try { + request.setCallingPackageName(mPackageName); final TextLanguageCallback callback = new TextLanguageCallback(); mManagerService.onDetectLanguage(mSessionId, request, callback); final TextLanguage textLanguage = callback.mReceiver.get(); @@ -168,6 +171,7 @@ public final class SystemTextClassifier implements TextClassifier { Utils.checkMainThread(); try { + request.setCallingPackageName(mPackageName); final ConversationActionsCallback callback = new ConversationActionsCallback(); mManagerService.onSuggestConversationActions(mSessionId, request, callback); final ConversationActions conversationActions = callback.mReceiver.get(); diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index e0910c0b37b7..d9f79655d588 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -37,11 +37,13 @@ import android.os.Bundle; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; +import android.text.SpannedString; import android.util.ArrayMap; import android.view.View.OnClickListener; import android.view.textclassifier.TextClassifier.EntityType; import android.view.textclassifier.TextClassifier.Utils; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; @@ -518,6 +520,7 @@ public final class TextClassification implements Parcelable { @Nullable private final LocaleList mDefaultLocales; @Nullable private final ZonedDateTime mReferenceTime; @NonNull private final Bundle mExtras; + @Nullable private String mCallingPackageName; private Request( CharSequence text, @@ -578,6 +581,26 @@ public final class TextClassification implements Parcelable { } /** + * Sets the name of the package that is sending this request. + * <p> + * For SystemTextClassifier's use. + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setCallingPackageName(@Nullable String callingPackageName) { + mCallingPackageName = callingPackageName; + } + + /** + * Returns the name of the package that sent this request. + * This returns {@code null} if no calling package name is set. + */ + @Nullable + public String getCallingPackageName() { + return mCallingPackageName; + } + + /** * Returns the extended data. * * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should @@ -660,7 +683,8 @@ public final class TextClassification implements Parcelable { */ @NonNull public Request build() { - return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales, mReferenceTime, + return new Request(new SpannedString(mText), mStartIndex, mEndIndex, + mDefaultLocales, mReferenceTime, mExtras == null ? Bundle.EMPTY : mExtras.deepCopy()); } } @@ -672,25 +696,37 @@ public final class TextClassification implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mText.toString()); + dest.writeCharSequence(mText); dest.writeInt(mStartIndex); dest.writeInt(mEndIndex); - dest.writeInt(mDefaultLocales != null ? 1 : 0); - if (mDefaultLocales != null) { - mDefaultLocales.writeToParcel(dest, flags); - } - dest.writeInt(mReferenceTime != null ? 1 : 0); - if (mReferenceTime != null) { - dest.writeString(mReferenceTime.toString()); - } + dest.writeParcelable(mDefaultLocales, flags); + dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString()); + dest.writeString(mCallingPackageName); dest.writeBundle(mExtras); } + private static Request readFromParcel(Parcel in) { + final CharSequence text = in.readCharSequence(); + final int startIndex = in.readInt(); + final int endIndex = in.readInt(); + final LocaleList defaultLocales = in.readParcelable(null); + final String referenceTimeString = in.readString(); + final ZonedDateTime referenceTime = referenceTimeString == null + ? null : ZonedDateTime.parse(referenceTimeString); + final String callingPackageName = in.readString(); + final Bundle extras = in.readBundle(); + + final Request request = new Request(text, startIndex, endIndex, + defaultLocales, referenceTime, extras); + request.setCallingPackageName(callingPackageName); + return request; + } + public static final Parcelable.Creator<Request> CREATOR = new Parcelable.Creator<Request>() { @Override public Request createFromParcel(Parcel in) { - return new Request(in); + return readFromParcel(in); } @Override @@ -698,15 +734,6 @@ public final class TextClassification implements Parcelable { return new Request[size]; } }; - - private Request(Parcel in) { - mText = in.readString(); - mStartIndex = in.readInt(); - mEndIndex = in.readInt(); - mDefaultLocales = in.readInt() == 0 ? null : LocaleList.CREATOR.createFromParcel(in); - mReferenceTime = in.readInt() == 0 ? null : ZonedDateTime.parse(in.readString()); - mExtras = in.readBundle(); - } } @Override diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 8e14dfdb7ee3..4e9ab5092777 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -374,7 +374,8 @@ public final class TextClassifierImpl implements TextClassifier { return mFallback.suggestConversationActions(request); } ActionsSuggestionsModel.ConversationMessage[] nativeMessages = - ActionsSuggestionsHelper.toNativeMessages(request.getConversation()); + ActionsSuggestionsHelper.toNativeMessages(request.getConversation(), + this::detectLanguageTagsFromText); if (nativeMessages.length == 0) { return mFallback.suggestConversationActions(request); } @@ -407,6 +408,26 @@ public final class TextClassifierImpl implements TextClassifier { return mFallback.suggestConversationActions(request); } + @Nullable + private String detectLanguageTagsFromText(CharSequence text) { + TextLanguage.Request request = new TextLanguage.Request.Builder(text).build(); + TextLanguage textLanguage = detectLanguage(request); + int localeHypothesisCount = textLanguage.getLocaleHypothesisCount(); + List<String> languageTags = new ArrayList<>(); + // TODO: Reconsider this and probably make the score threshold configurable. + for (int i = 0; i < localeHypothesisCount; i++) { + ULocale locale = textLanguage.getLocale(i); + if (textLanguage.getConfidenceScore(locale) < 0.5) { + break; + } + languageTags.add(locale.toLanguageTag()); + } + if (languageTags.isEmpty()) { + return LocaleList.getDefault().toLanguageTags(); + } + return String.join(",", languageTags); + } + private Collection<String> resolveActionTypesFromRequest(ConversationActions.Request request) { List<String> defaultActionTypes = request.getHints().contains(ConversationActions.HINT_FOR_NOTIFICATION) diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java index d28459e733f0..b1609fcfe56a 100644 --- a/core/java/android/view/textclassifier/TextLanguage.java +++ b/core/java/android/view/textclassifier/TextLanguage.java @@ -26,6 +26,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.util.Locale; @@ -90,7 +91,7 @@ public final class TextLanguage implements Parcelable { * confidence to low confidence. * * @throws IndexOutOfBoundsException if the specified index is out of range. - * @see #getLocaleCount() for the number of locales available. + * @see #getLocaleHypothesisCount() for the number of locales available. */ @NonNull public ULocale getLocale(int index) { @@ -222,11 +223,12 @@ public final class TextLanguage implements Parcelable { }; private final CharSequence mText; - private final Bundle mBundle; + private final Bundle mExtra; + @Nullable private String mCallingPackageName; private Request(CharSequence text, Bundle bundle) { mText = text; - mBundle = bundle; + mExtra = bundle; } /** @@ -238,6 +240,25 @@ public final class TextLanguage implements Parcelable { } /** + * Sets the name of the package that is sending this request. + * Package-private for SystemTextClassifier's use. + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setCallingPackageName(@Nullable String callingPackageName) { + mCallingPackageName = callingPackageName; + } + + /** + * Returns the name of the package that sent this request. + * This returns null if no calling package name is set. + */ + @Nullable + public String getCallingPackageName() { + return mCallingPackageName; + } + + /** * Returns a bundle containing non-structured extra information about this request. * * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should @@ -246,7 +267,7 @@ public final class TextLanguage implements Parcelable { */ @NonNull public Bundle getExtras() { - return mBundle.deepCopy(); + return mExtra.deepCopy(); } @Override @@ -257,13 +278,18 @@ public final class TextLanguage implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeCharSequence(mText); - dest.writeBundle(mBundle); + dest.writeString(mCallingPackageName); + dest.writeBundle(mExtra); } private static Request readFromParcel(Parcel in) { - return new Request( - in.readCharSequence(), - in.readBundle()); + final CharSequence text = in.readCharSequence(); + final String callingPackageName = in.readString(); + final Bundle extra = in.readBundle(); + + final Request request = new Request(text, extra); + request.setCallingPackageName(callingPackageName); + return request; } /** diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index 1e42c414a365..ab341782ac58 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -30,6 +30,7 @@ import android.text.method.MovementMethod; import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.view.View; +import android.view.textclassifier.TextClassifier.EntityConfig; import android.view.textclassifier.TextClassifier.EntityType; import android.widget.TextView; @@ -205,27 +206,32 @@ public final class TextLinks implements Parcelable { private final EntityConfidence mEntityScores; private final int mStart; private final int mEnd; - @Nullable final URLSpan mUrlSpan; + private final Bundle mExtras; + @Nullable private final URLSpan mUrlSpan; /** * Create a new TextLink. * * @param start The start index of the identified subsequence * @param end The end index of the identified subsequence - * @param entityScores A mapping of entity type to confidence score + * @param entityConfidence A mapping of entity type to confidence score + * @param extras A bundle containing custom data related to this TextLink * @param urlSpan An optional URLSpan to delegate to. NOTE: Not parcelled * - * @throws IllegalArgumentException if entityScores is null or empty + * @throws IllegalArgumentException if {@code entityConfidence} is null or empty + * @throws IllegalArgumentException if {@code start} is greater than {@code end} */ - TextLink(int start, int end, Map<String, Float> entityScores, - @Nullable URLSpan urlSpan) { - Preconditions.checkNotNull(entityScores); - Preconditions.checkArgument(!entityScores.isEmpty()); + private TextLink(int start, int end, @NonNull EntityConfidence entityConfidence, + @NonNull Bundle extras, @Nullable URLSpan urlSpan) { + Preconditions.checkNotNull(entityConfidence); + Preconditions.checkArgument(!entityConfidence.getEntities().isEmpty()); Preconditions.checkArgument(start <= end); + Preconditions.checkNotNull(extras); mStart = start; mEnd = end; - mEntityScores = new EntityConfidence(entityScores); + mEntityScores = entityConfidence; mUrlSpan = urlSpan; + mExtras = extras; } /** @@ -274,6 +280,13 @@ public final class TextLinks implements Parcelable { return mEntityScores.getConfidenceScore(entityType); } + /** + * Returns a bundle containing custom data related to this TextLink. + */ + public Bundle getExtras() { + return mExtras; + } + @Override public String toString() { return String.format(Locale.US, @@ -291,13 +304,22 @@ public final class TextLinks implements Parcelable { mEntityScores.writeToParcel(dest, flags); dest.writeInt(mStart); dest.writeInt(mEnd); + dest.writeBundle(mExtras); + } + + private static TextLink readFromParcel(Parcel in) { + final EntityConfidence entityConfidence = EntityConfidence.CREATOR.createFromParcel(in); + final int start = in.readInt(); + final int end = in.readInt(); + final Bundle extras = in.readBundle(); + return new TextLink(start, end, entityConfidence, extras, null /* urlSpan */); } public static final Parcelable.Creator<TextLink> CREATOR = new Parcelable.Creator<TextLink>() { @Override public TextLink createFromParcel(Parcel in) { - return new TextLink(in); + return readFromParcel(in); } @Override @@ -305,13 +327,6 @@ public final class TextLinks implements Parcelable { return new TextLink[size]; } }; - - private TextLink(Parcel in) { - mEntityScores = EntityConfidence.CREATOR.createFromParcel(in); - mStart = in.readInt(); - mEnd = in.readInt(); - mUrlSpan = null; - } } /** @@ -321,23 +336,21 @@ public final class TextLinks implements Parcelable { private final CharSequence mText; @Nullable private final LocaleList mDefaultLocales; - @Nullable private final TextClassifier.EntityConfig mEntityConfig; + @Nullable private final EntityConfig mEntityConfig; private final boolean mLegacyFallback; - private String mCallingPackageName; + @Nullable private String mCallingPackageName; private final Bundle mExtras; private Request( CharSequence text, LocaleList defaultLocales, - TextClassifier.EntityConfig entityConfig, + EntityConfig entityConfig, boolean legacyFallback, - String callingPackageName, Bundle extras) { mText = text; mDefaultLocales = defaultLocales; mEntityConfig = entityConfig; mLegacyFallback = legacyFallback; - mCallingPackageName = callingPackageName; mExtras = extras; } @@ -360,10 +373,10 @@ public final class TextLinks implements Parcelable { /** * @return The config representing the set of entities to look for - * @see Builder#setEntityConfig(TextClassifier.EntityConfig) + * @see Builder#setEntityConfig(EntityConfig) */ @Nullable - public TextClassifier.EntityConfig getEntityConfig() { + public EntityConfig getEntityConfig() { return mEntityConfig; } @@ -378,13 +391,26 @@ public final class TextLinks implements Parcelable { } /** - * Sets the name of the package that requested the links to get generated. + * Sets the name of the package that is sending this request. + * <p> + * Package-private for SystemTextClassifier's use. + * @hide */ - void setCallingPackageName(@Nullable String callingPackageName) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setCallingPackageName(@Nullable String callingPackageName) { mCallingPackageName = callingPackageName; } /** + * Returns the name of the package that sent this request. + * This returns {@code null} if no calling package name is set. + */ + @Nullable + public String getCallingPackageName() { + return mCallingPackageName; + } + + /** * Returns the extended data. * * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should @@ -404,9 +430,8 @@ public final class TextLinks implements Parcelable { private final CharSequence mText; @Nullable private LocaleList mDefaultLocales; - @Nullable private TextClassifier.EntityConfig mEntityConfig; + @Nullable private EntityConfig mEntityConfig; private boolean mLegacyFallback = true; // Use legacy fall back by default. - private String mCallingPackageName; @Nullable private Bundle mExtras; public Builder(@NonNull CharSequence text) { @@ -434,7 +459,7 @@ public final class TextLinks implements Parcelable { * @return this builder */ @NonNull - public Builder setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) { + public Builder setEntityConfig(@Nullable EntityConfig entityConfig) { mEntityConfig = entityConfig; return this; } @@ -455,18 +480,6 @@ public final class TextLinks implements Parcelable { } /** - * Sets the name of the package that requested the links to get generated. - * - * @return this builder - * @hide - */ - @NonNull - public Builder setCallingPackageName(@Nullable String callingPackageName) { - mCallingPackageName = callingPackageName; - return this; - } - - /** * Sets the extended data. * * @return this builder @@ -483,21 +496,11 @@ public final class TextLinks implements Parcelable { public Request build() { return new Request( mText, mDefaultLocales, mEntityConfig, - mLegacyFallback, mCallingPackageName, + mLegacyFallback, mExtras == null ? Bundle.EMPTY : mExtras.deepCopy()); } } - /** - * @return the name of the package that requested the links to get generated. - * TODO: make available as system API - * @hide - */ - @Nullable - public String getCallingPackageName() { - return mCallingPackageName; - } - @Override public int describeContents() { return 0; @@ -506,23 +509,30 @@ public final class TextLinks implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mText.toString()); - dest.writeInt(mDefaultLocales != null ? 1 : 0); - if (mDefaultLocales != null) { - mDefaultLocales.writeToParcel(dest, flags); - } - dest.writeInt(mEntityConfig != null ? 1 : 0); - if (mEntityConfig != null) { - mEntityConfig.writeToParcel(dest, flags); - } + dest.writeParcelable(mDefaultLocales, flags); + dest.writeParcelable(mEntityConfig, flags); dest.writeString(mCallingPackageName); dest.writeBundle(mExtras); } + private static Request readFromParcel(Parcel in) { + final String text = in.readString(); + final LocaleList defaultLocales = in.readParcelable(null); + final EntityConfig entityConfig = in.readParcelable(null); + final String callingPackageName = in.readString(); + final Bundle extras = in.readBundle(); + + final Request request = new Request(text, defaultLocales, entityConfig, + /* legacyFallback= */ true, extras); + request.setCallingPackageName(callingPackageName); + return request; + } + public static final Parcelable.Creator<Request> CREATOR = new Parcelable.Creator<Request>() { @Override public Request createFromParcel(Parcel in) { - return new Request(in); + return readFromParcel(in); } @Override @@ -530,16 +540,6 @@ public final class TextLinks implements Parcelable { return new Request[size]; } }; - - private Request(Parcel in) { - mText = in.readString(); - mDefaultLocales = in.readInt() == 0 ? null : LocaleList.CREATOR.createFromParcel(in); - mEntityConfig = in.readInt() == 0 - ? null : TextClassifier.EntityConfig.CREATOR.createFromParcel(in); - mLegacyFallback = true; - mCallingPackageName = in.readString(); - mExtras = in.readBundle(); - } } /** @@ -645,9 +645,20 @@ public final class TextLinks implements Parcelable { * @throws IllegalArgumentException if entityScores is null or empty. */ @NonNull - public Builder addLink(int start, int end, Map<String, Float> entityScores) { - mLinks.add(new TextLink(start, end, entityScores, null)); - return this; + public Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores) { + return addLink(start, end, entityScores, Bundle.EMPTY, null); + } + + /** + * Adds a TextLink. + * + * @see #addLink(int, int, Map) + * @param extras An optional bundle containing custom data related to this TextLink + */ + @NonNull + public Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores, + @NonNull Bundle extras) { + return addLink(start, end, entityScores, extras, null); } /** @@ -655,9 +666,15 @@ public final class TextLinks implements Parcelable { * @param urlSpan An optional URLSpan to delegate to. NOTE: Not parcelled. */ @NonNull - Builder addLink(int start, int end, Map<String, Float> entityScores, + Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores, @Nullable URLSpan urlSpan) { - mLinks.add(new TextLink(start, end, entityScores, urlSpan)); + return addLink(start, end, entityScores, Bundle.EMPTY, urlSpan); + } + + private Builder addLink(int start, int end, @NonNull Map<String, Float> entityScores, + @NonNull Bundle extras, @Nullable URLSpan urlSpan) { + mLinks.add(new TextLink( + start, end, new EntityConfidence(entityScores), extras, urlSpan)); return this; } diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java index f23691586a2a..4a6f3e53b223 100644 --- a/core/java/android/view/textclassifier/TextSelection.java +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -24,10 +24,12 @@ import android.os.Bundle; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; +import android.text.SpannedString; import android.util.ArrayMap; import android.view.textclassifier.TextClassifier.EntityType; import android.view.textclassifier.TextClassifier.Utils; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.util.Locale; @@ -209,6 +211,7 @@ public final class TextSelection implements Parcelable { @Nullable private final LocaleList mDefaultLocales; private final boolean mDarkLaunchAllowed; private final Bundle mExtras; + @Nullable private String mCallingPackageName; private Request( CharSequence text, @@ -270,6 +273,26 @@ public final class TextSelection implements Parcelable { } /** + * Sets the name of the package that is sending this request. + * <p> + * Package-private for SystemTextClassifier's use. + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setCallingPackageName(@Nullable String callingPackageName) { + mCallingPackageName = callingPackageName; + } + + /** + * Returns the name of the package that sent this request. + * This returns {@code null} if no calling package name is set. + */ + @Nullable + public String getCallingPackageName() { + return mCallingPackageName; + } + + /** * Returns the extended data. * * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should @@ -355,7 +378,7 @@ public final class TextSelection implements Parcelable { */ @NonNull public Request build() { - return new Request(mText, mStartIndex, mEndIndex, + return new Request(new SpannedString(mText), mStartIndex, mEndIndex, mDefaultLocales, mDarkLaunchAllowed, mExtras == null ? Bundle.EMPTY : mExtras.deepCopy()); } @@ -368,21 +391,33 @@ public final class TextSelection implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mText.toString()); + dest.writeCharSequence(mText); dest.writeInt(mStartIndex); dest.writeInt(mEndIndex); - dest.writeInt(mDefaultLocales != null ? 1 : 0); - if (mDefaultLocales != null) { - mDefaultLocales.writeToParcel(dest, flags); - } + dest.writeParcelable(mDefaultLocales, flags); + dest.writeString(mCallingPackageName); dest.writeBundle(mExtras); } + private static Request readFromParcel(Parcel in) { + final CharSequence text = in.readCharSequence(); + final int startIndex = in.readInt(); + final int endIndex = in.readInt(); + final LocaleList defaultLocales = in.readParcelable(null); + final String callingPackageName = in.readString(); + final Bundle extras = in.readBundle(); + + final Request request = new Request(text, startIndex, endIndex, defaultLocales, + /* darkLaunchAllowed= */ false, extras); + request.setCallingPackageName(callingPackageName); + return request; + } + public static final Parcelable.Creator<Request> CREATOR = new Parcelable.Creator<Request>() { @Override public Request createFromParcel(Parcel in) { - return new Request(in); + return readFromParcel(in); } @Override @@ -390,15 +425,6 @@ public final class TextSelection implements Parcelable { return new Request[size]; } }; - - private Request(Parcel in) { - mText = in.readString(); - mStartIndex = in.readInt(); - mEndIndex = in.readInt(); - mDefaultLocales = in.readInt() == 0 ? null : LocaleList.CREATOR.createFromParcel(in); - mDarkLaunchAllowed = false; - mExtras = in.readBundle(); - } } @Override diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 90cf871830fd..90da81276ba3 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -157,6 +157,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AnimationUtils; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; +import android.view.contentcapture.ContentCaptureManager; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; @@ -166,7 +167,6 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.view.intelligence.ContentCaptureManager; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationContext; import android.view.textclassifier.TextClassificationManager; @@ -6028,14 +6028,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTextDir == null) { mTextDir = getTextDirectionHeuristic(); } - if (!precomputed.getParams().isSameTextMetricsInternal( - getPaint(), mTextDir, mBreakStrategy, mHyphenationFrequency)) { - throw new IllegalArgumentException( + final @PrecomputedText.Params.CheckResultUsableResult int checkResult = + precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, + mHyphenationFrequency); + switch (checkResult) { + case PrecomputedText.Params.UNUSABLE: + throw new IllegalArgumentException( "PrecomputedText's Parameters don't match the parameters of this TextView." + "Consider using setTextMetricsParams(precomputedText.getParams()) " + "to override the settings of this TextView: " + "PrecomputedText: " + precomputed.getParams() + "TextView: " + getTextMetricsParams()); + case PrecomputedText.Params.NEED_RECOMPUTE: + precomputed = PrecomputedText.create(precomputed, getTextMetricsParams()); + break; + case PrecomputedText.Params.USABLE: + // pass through } } else if (type == BufferType.SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text); @@ -10209,7 +10217,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // ContentCapture - if (isImportantForContentCapture() && isTextEditable()) { + if (isLaidOut() && isImportantForContentCapture() && isTextEditable()) { final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class); if (cm != null && cm.isContentCaptureEnabled()) { // TODO(b/111276913): pass flags when edited by user / add CTS test diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 46c6fa42babf..ef5eb6cb5780 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -94,7 +94,7 @@ import java.util.Set; public class ResolverActivity extends Activity { // Temporary flag for new chooser delegate behavior. - boolean mEnableChooserDelegate = false; + boolean mEnableChooserDelegate = true; protected ResolveListAdapter mAdapter; private boolean mSafeForwardingMode; @@ -887,6 +887,8 @@ public class ResolverActivity extends Activity { chooserIntent.putExtra(ActivityTaskManager.EXTRA_IGNORE_TARGET_SECURITY, ignoreTargetSecurity); chooserIntent.putExtra(Intent.EXTRA_USER_ID, userId); + chooserIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT + | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); startActivity(chooserIntent); } catch (RemoteException e) { Log.e(TAG, e.toString()); diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index 8bc90a891352..a27dbeae2f3a 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -39,6 +39,7 @@ import android.provider.DocumentsContract.Document; import android.provider.DocumentsProvider; import android.provider.MediaStore; import android.provider.MetadataReader; +import android.system.Int64Ref; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -53,6 +54,12 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -122,18 +129,56 @@ public abstract class FileSystemProvider extends DocumentsProvider { throw new FileNotFoundException("Can't find the file for documentId: " + documentId); } + final String mimeType = getDocumentType(documentId); + if (Document.MIME_TYPE_DIR.equals(mimeType)) { + final Int64Ref treeCount = new Int64Ref(0); + final Int64Ref treeSize = new Int64Ref(0); + try { + final Path path = FileSystems.getDefault().getPath(file.getAbsolutePath()); + Files.walkFileTree(path, new FileVisitor<Path>() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + treeCount.value += 1; + treeSize.value += attrs.size(); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + Log.e(TAG, "An error occurred retrieving the metadata", e); + return null; + } + + final Bundle res = new Bundle(); + res.putLong(DocumentsContract.METADATA_TREE_COUNT, treeCount.value); + res.putLong(DocumentsContract.METADATA_TREE_SIZE, treeSize.value); + return res; + } + if (!file.isFile()) { Log.w(TAG, "Can't stream non-regular file. Returning empty metadata."); return null; } - if (!file.canRead()) { Log.w(TAG, "Can't stream non-readable file. Returning empty metadata."); return null; } - - String mimeType = getDocumentType(documentId); if (!MetadataReader.isSupportedMimeType(mimeType)) { + Log.w(TAG, "Unsupported type " + mimeType + ". Returning empty metadata."); return null; } @@ -562,7 +607,8 @@ public abstract class FileSystemProvider extends DocumentsProvider { } protected boolean typeSupportsMetadata(String mimeType) { - return MetadataReader.isSupportedMimeType(mimeType); + return MetadataReader.isSupportedMimeType(mimeType) + || Document.MIME_TYPE_DIR.equals(mimeType); } protected final File getFileForDocId(String docId) throws FileNotFoundException { diff --git a/core/java/com/android/internal/os/AppIdToPackageMap.java b/core/java/com/android/internal/os/AppIdToPackageMap.java new file mode 100644 index 000000000000..65aa989bbb38 --- /dev/null +++ b/core/java/com/android/internal/os/AppIdToPackageMap.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + + +import android.app.AppGlobals; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.os.UserHandle; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Maps AppIds to their package names. */ +public final class AppIdToPackageMap { + private final Map<Integer, String> mAppIdToPackageMap; + + @VisibleForTesting + public AppIdToPackageMap(Map<Integer, String> appIdToPackageMap) { + mAppIdToPackageMap = appIdToPackageMap; + } + + /** Creates a new {@link AppIdToPackageMap} for currently installed packages. */ + public static AppIdToPackageMap getSnapshot() { + List<PackageInfo> packages; + try { + packages = AppGlobals.getPackageManager() + .getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE, + UserHandle.USER_SYSTEM).getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + final Map<Integer, String> map = new HashMap<>(); + for (PackageInfo pkg : packages) { + final int uid = pkg.applicationInfo.uid; + if (pkg.sharedUserId != null && map.containsKey(uid)) { + // Use sharedUserId string as package name if there are collisions + map.put(uid, "shared:" + pkg.sharedUserId); + } else { + map.put(uid, pkg.packageName); + } + } + return new AppIdToPackageMap(map); + } + + /** Maps the AppId to a package name. */ + public String mapAppId(int appId) { + String pkgName = mAppIdToPackageMap.get(appId); + return pkgName == null ? String.valueOf(appId) : pkgName; + } + + /** Maps the UID to a package name. */ + public String mapUid(int uid) { + final int appId = UserHandle.getAppId(uid); + final String pkgName = mAppIdToPackageMap.get(appId); + final String uidStr = UserHandle.formatUid(uid); + return pkgName == null ? uidStr : pkgName + '/' + uidStr; + } +} diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index ff34036ce7e9..5465485790be 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -21,8 +21,6 @@ import android.annotation.Nullable; import android.os.Binder; import android.os.Process; import android.os.SystemClock; -import android.os.ThreadLocalWorkSource; -import android.os.UserHandle; import android.text.format.DateFormat; import android.util.ArrayMap; import android.util.Pair; @@ -62,7 +60,11 @@ public class BinderCallsStats implements BinderInternal.Observer { private static final int CALL_SESSIONS_POOL_SIZE = 100; private static final int MAX_EXCEPTION_COUNT_SIZE = 50; private static final String EXCEPTION_COUNT_OVERFLOW_NAME = "overflow"; + // Default values for overflow entry. The work source uid does not use a default value in order + // to have on overflow entry per work source uid. private static final Class<? extends Binder> OVERFLOW_BINDER = OverflowBinder.class; + private static final boolean OVERFLOW_SCREEN_INTERACTIVE = false; + private static final int OVERFLOW_DIRECT_CALLING_UID = -1; private static final int OVERFLOW_TRANSACTION_CODE = -1; // Whether to collect all the data: cpu + exceptions + reply/request sizes. @@ -106,7 +108,7 @@ public class BinderCallsStats implements BinderInternal.Observer { @Override @Nullable - public CallSession callStarted(Binder binder, int code) { + public CallSession callStarted(Binder binder, int code, int workSourceUid) { if (mDeviceState == null || mDeviceState.isCharging()) { return null; } @@ -130,19 +132,21 @@ public class BinderCallsStats implements BinderInternal.Observer { } @Override - public void callEnded(@Nullable CallSession s, int parcelRequestSize, int parcelReplySize) { + public void callEnded(@Nullable CallSession s, int parcelRequestSize, + int parcelReplySize, int workSourceUid) { if (s == null) { return; } - processCallEnded(s, parcelRequestSize, parcelReplySize); + processCallEnded(s, parcelRequestSize, parcelReplySize, workSourceUid); if (mCallSessionsPool.size() < CALL_SESSIONS_POOL_SIZE) { mCallSessionsPool.add(s); } } - private void processCallEnded(CallSession s, int parcelRequestSize, int parcelReplySize) { + private void processCallEnded(CallSession s, + int parcelRequestSize, int parcelReplySize, int workSourceUid) { // Non-negative time signals we need to record data for this call. final boolean recordCall = s.cpuTimeStarted >= 0; final long duration; @@ -155,7 +159,6 @@ public class BinderCallsStats implements BinderInternal.Observer { latencyDuration = 0; } final int callingUid = getCallingUid(); - final int workSourceUid = getWorkSourceUid(); synchronized (mLock) { // This was already checked in #callStart but check again while synchronized. @@ -356,14 +359,13 @@ public class BinderCallsStats implements BinderInternal.Observer { } /** Writes the collected statistics to the supplied {@link PrintWriter}.*/ - public void dump(PrintWriter pw, Map<Integer, String> appIdToPkgNameMap, boolean verbose) { + public void dump(PrintWriter pw, AppIdToPackageMap packageMap, boolean verbose) { synchronized (mLock) { - dumpLocked(pw, appIdToPkgNameMap, verbose); + dumpLocked(pw, packageMap, verbose); } } - private void dumpLocked(PrintWriter pw, Map<Integer, String> appIdToPkgNameMap, - boolean verbose) { + private void dumpLocked(PrintWriter pw, AppIdToPackageMap packageMap, boolean verbose) { long totalCallsCount = 0; long totalRecordedCallsCount = 0; long totalCpuTime = 0; @@ -397,9 +399,9 @@ public class BinderCallsStats implements BinderInternal.Observer { for (ExportedCallStat e : exportedCallStats) { sb.setLength(0); sb.append(" ") - .append(uidToString(e.callingUid, appIdToPkgNameMap)) + .append(packageMap.mapUid(e.callingUid)) .append(',') - .append(uidToString(e.workSourceUid, appIdToPkgNameMap)) + .append(packageMap.mapUid(e.workSourceUid)) .append(',').append(e.className) .append('#').append(e.methodName) .append(',').append(e.screenInteractive) @@ -420,7 +422,7 @@ public class BinderCallsStats implements BinderInternal.Observer { final List<UidEntry> summaryEntries = verbose ? entries : getHighestValues(entries, value -> value.cpuTimeMicros, 0.9); for (UidEntry entry : summaryEntries) { - String uidStr = uidToString(entry.workSourceUid, appIdToPkgNameMap); + String uidStr = packageMap.mapUid(entry.workSourceUid); pw.println(String.format(" %10d %3.0f%% %8d %8d %s", entry.cpuTimeMicros, 100d * entry.cpuTimeMicros / totalCpuTime, entry.recordedCallCount, entry.callCount, uidStr)); @@ -448,13 +450,6 @@ public class BinderCallsStats implements BinderInternal.Observer { } } - private static String uidToString(int uid, Map<Integer, String> pkgNameMap) { - final int appId = UserHandle.getAppId(uid); - final String pkgName = pkgNameMap == null ? null : pkgNameMap.get(appId); - final String uidStr = UserHandle.formatUid(uid); - return pkgName == null ? uidStr : pkgName + '/' + uidStr; - } - protected long getThreadTimeMicro() { return SystemClock.currentThreadTimeMicro(); } @@ -463,10 +458,6 @@ public class BinderCallsStats implements BinderInternal.Observer { return Binder.getCallingUid(); } - protected int getWorkSourceUid() { - return ThreadLocalWorkSource.getUid(); - } - protected long getElapsedRealtimeMicro() { return SystemClock.elapsedRealtimeNanos() / 1000; } @@ -669,14 +660,16 @@ public class BinderCallsStats implements BinderInternal.Observer { // Only create CallStat if it's a new entry, otherwise update existing instance. if (mapCallStat == null) { if (maxCallStatsReached) { - mapCallStat = get(callingUid, OVERFLOW_BINDER, OVERFLOW_TRANSACTION_CODE, - screenInteractive); + mapCallStat = get(OVERFLOW_DIRECT_CALLING_UID, OVERFLOW_BINDER, + OVERFLOW_TRANSACTION_CODE, OVERFLOW_SCREEN_INTERACTIVE); if (mapCallStat != null) { return mapCallStat; } + callingUid = OVERFLOW_DIRECT_CALLING_UID; binderClass = OVERFLOW_BINDER; transactionCode = OVERFLOW_TRANSACTION_CODE; + screenInteractive = OVERFLOW_SCREEN_INTERACTIVE; } mapCallStat = new CallStat(callingUid, binderClass, transactionCode, diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java index 015506722c73..5b699791ea77 100644 --- a/core/java/com/android/internal/os/BinderInternal.java +++ b/core/java/com/android/internal/os/BinderInternal.java @@ -22,7 +22,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.SystemClock; import android.util.EventLog; -import android.util.Log; import android.util.SparseIntArray; import com.android.internal.util.Preconditions; @@ -86,6 +85,22 @@ public class BinderInternal { boolean exceptionThrown; } + + /** + * Responsible for resolving a work source. + */ + @FunctionalInterface + public interface WorkSourceProvider { + /** + * <p>This method is called in a critical path of the binder transaction. + * <p>The implementation should never execute a binder call since it is called during a + * binder transaction. + * + * @return the uid of the process to attribute the binder transaction to. + */ + int resolveWorkSourceUid(); + } + /** * Allows to track various steps of an API call. */ @@ -95,14 +110,16 @@ public class BinderInternal { * * @return a CallSession to pass to the callEnded method. */ - CallSession callStarted(Binder binder, int code); + CallSession callStarted(Binder binder, int code, int workSourceUid); /** * Called when a binder call stops. * - * <li>This method will be called even when an exception is thrown. + * <li>This method will be called even when an exception is thrown by the binder stub + * implementation. */ - void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize); + void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize, + int workSourceUid); /** * Called if an exception is thrown while executing the binder transaction. diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 1e71bd171eea..f62c4402cb4e 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -64,6 +64,8 @@ interface IInputMethodManager { void showInputMethodPickerFromClient(in IInputMethodClient client, int auxiliarySubtypeMode); + void showInputMethodPickerFromSystem(in IInputMethodClient client, int auxiliarySubtypeMode, + int displayId); void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId); boolean isInputMethodPickerShownForTest(); // TODO(Bug 114488811): this can be removed once we deprecate special null token rule. @@ -74,10 +76,6 @@ interface IInputMethodManager { boolean notifySuggestionPicked(in SuggestionSpan span, String originalString, int index); InputMethodSubtype getCurrentInputMethodSubtype(); boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype); - // TODO(Bug 114488811): this can be removed once we deprecate special null token rule. - boolean switchToPreviousInputMethod(in IBinder token); - // TODO(Bug 114488811): this can be removed once we deprecate special null token rule. - boolean switchToNextInputMethod(in IBinder token, boolean onlyCurrentIme); void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); // This is kept due to @UnsupportedAppUsage. // TODO(Bug 113914148): Consider removing this. diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index c96bacd636d3..d5dc703408e4 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -840,6 +840,11 @@ public class LockPatternUtils { + "of length " + MIN_LOCK_PASSWORD_SIZE); } + if (requestedQuality < PASSWORD_QUALITY_NUMERIC) { + throw new IllegalArgumentException("quality must be at least NUMERIC, but was " + + requestedQuality); + } + final int currentQuality = getKeyguardStoredPasswordQuality(userHandle); setKeyguardStoredPasswordQuality( computePasswordQuality(CREDENTIAL_TYPE_PASSWORD, password, requestedQuality), diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index b97a9fa8d1cc..2e674a5892c6 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -69,6 +69,9 @@ public class SystemConfig { private static final int ALLOW_HIDDENAPI_WHITELISTING = 0x40; private static final int ALLOW_ALL = ~0; + // property for runtime configuration differentiation + private static final String SKU_PROPERTY = "ro.boot.product.hardware.sku"; + // Group-ids that are given to all packages as read from etc/permissions/*.xml. int[] mGlobalGids; @@ -78,10 +81,23 @@ public class SystemConfig { final ArrayList<SplitPermissionInfo> mSplitPermissions = new ArrayList<>(); + public static final class SharedLibraryEntry { + public final String name; + public final String filename; + public final String[] dependencies; + + SharedLibraryEntry(String name, String filename, String[] dependencies) { + this.name = name; + this.filename = filename; + this.dependencies = dependencies; + } + } + // These are the built-in shared libraries that were read from the - // system configuration files. Keys are the library names; strings are the - // paths to the libraries. - final ArrayMap<String, String> mSharedLibraries = new ArrayMap<>(); + // system configuration files. Keys are the library names; values are + // the individual entries that contain information such as filename + // and dependencies. + final ArrayMap<String, SharedLibraryEntry> mSharedLibraries = new ArrayMap<>(); // These are the features this devices supports that were read from the // system configuration files. @@ -200,7 +216,7 @@ public class SystemConfig { return mSplitPermissions; } - public ArrayMap<String, String> getSharedLibraries() { + public ArrayMap<String, SharedLibraryEntry> getSharedLibraries() { return mSharedLibraries; } @@ -332,6 +348,17 @@ public class SystemConfig { readPermissions(Environment.buildPath( Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag); + String skuProperty = SystemProperties.get(SKU_PROPERTY, ""); + if (!skuProperty.isEmpty()) { + String skuDir = "sku_" + skuProperty; + + readPermissions(Environment.buildPath( + Environment.getOdmDirectory(), "etc", "sysconfig", skuDir), odmPermissionFlag); + readPermissions(Environment.buildPath( + Environment.getOdmDirectory(), "etc", "permissions", skuDir), + odmPermissionFlag); + } + // Allow OEM to customize features and OEM permissions int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS; readPermissions(Environment.buildPath( @@ -368,6 +395,10 @@ public class SystemConfig { // Iterate over the files in the directory and scan .xml files File platformFile = null; for (File f : libraryDir.listFiles()) { + if (!f.isFile()) { + continue; + } + // We'll read platform.xml last if (f.getPath().endsWith("etc/permissions/platform.xml")) { platformFile = f; @@ -497,6 +528,7 @@ public class SystemConfig { } else if ("library".equals(name) && allowLibs) { String lname = parser.getAttributeValue(null, "name"); String lfile = parser.getAttributeValue(null, "file"); + String ldependency = parser.getAttributeValue(null, "dependency"); if (lname == null) { Slog.w(TAG, "<library> without name in " + permFile + " at " + parser.getPositionDescription()); @@ -505,11 +537,12 @@ public class SystemConfig { + parser.getPositionDescription()); } else { //Log.i(TAG, "Got library " + lname + " in " + lfile); - mSharedLibraries.put(lname, lfile); + SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile, + ldependency == null ? new String[0] : ldependency.split(":")); + mSharedLibraries.put(lname, entry); } XmlUtils.skipCurrentTag(parser); continue; - } else if ("feature".equals(name) && allowFeatures) { String fname = parser.getAttributeValue(null, "name"); int fversion = XmlUtils.readIntAttribute(parser, "version", 0); diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 31bb1d57d702..ad980355d3f3 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -132,7 +132,6 @@ cc_library_shared { "android/graphics/GIFMovie.cpp", "android/graphics/GraphicBuffer.cpp", "android/graphics/Graphics.cpp", - "android/graphics/HarfBuzzNGFaceSkia.cpp", "android/graphics/ImageDecoder.cpp", "android/graphics/Interpolator.cpp", "android/graphics/MaskFilter.cpp", @@ -264,6 +263,7 @@ cc_library_shared { "libEGL", "libGLESv1_CM", "libGLESv2", + "libGLESv3", "libvulkan", "libziparchive", "libETC1", diff --git a/core/jni/android/graphics/HarfBuzzNGFaceSkia.cpp b/core/jni/android/graphics/HarfBuzzNGFaceSkia.cpp deleted file mode 100644 index cfe742d0134d..000000000000 --- a/core/jni/android/graphics/HarfBuzzNGFaceSkia.cpp +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 2012 Google Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#define LOG_TAG "TextLayoutCache" - -#include "HarfBuzzNGFaceSkia.h" - -#include <stdlib.h> - -#include <log/log.h> - -#include <SkPaint.h> -#include <SkPath.h> -#include <SkPoint.h> -#include <SkRect.h> -#include <SkTypeface.h> - -#include <hb.h> - -namespace android { - -static const bool kDebugGlyphs = false; - -// Our implementation of the callbacks which Harfbuzz requires by using Skia -// calls. See the Harfbuzz source for references about what these callbacks do. - -struct HarfBuzzFontData { - explicit HarfBuzzFontData(SkPaint* paint) : m_paint(paint) { } - SkPaint* m_paint; -}; - -static void SkiaGetGlyphWidthAndExtents(SkPaint* paint, hb_codepoint_t codepoint, hb_position_t* width, hb_glyph_extents_t* extents) -{ - ALOG_ASSERT(codepoint <= 0xFFFF); - paint->setTextEncoding(kGlyphID_SkTextEncoding); - - SkScalar skWidth; - SkRect skBounds; - uint16_t glyph = codepoint; - - paint->getTextWidths(&glyph, sizeof(glyph), &skWidth, &skBounds); - if (kDebugGlyphs) { - ALOGD("returned glyph for %i: width = %f", codepoint, skWidth); - } - if (width) - *width = SkScalarToHBFixed(skWidth); - if (extents) { - // Invert y-axis because Skia is y-grows-down but we set up harfbuzz to be y-grows-up. - extents->x_bearing = SkScalarToHBFixed(skBounds.fLeft); - extents->y_bearing = SkScalarToHBFixed(-skBounds.fTop); - extents->width = SkScalarToHBFixed(skBounds.width()); - extents->height = SkScalarToHBFixed(-skBounds.height()); - } -} - -static hb_bool_t harfbuzzGetGlyph(hb_font_t* hbFont, void* fontData, hb_codepoint_t unicode, hb_codepoint_t variationSelector, hb_codepoint_t* glyph, void* userData) -{ - HarfBuzzFontData* hbFontData = reinterpret_cast<HarfBuzzFontData*>(fontData); - SkPaint* paint = hbFontData->m_paint; - paint->setTextEncoding(kUTF32_SkTextEncoding); - - if (unicode > 0x10ffff) { - unicode = 0xfffd; - } - SkUnichar unichar = unicode; - - uint16_t glyph16; - paint->textToGlyphs(&unichar, sizeof(unichar), &glyph16); - *glyph = glyph16; - return !!*glyph; -} - -static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* hbFont, void* fontData, hb_codepoint_t glyph, void* userData) -{ - HarfBuzzFontData* hbFontData = reinterpret_cast<HarfBuzzFontData*>(fontData); - hb_position_t advance = 0; - - SkiaGetGlyphWidthAndExtents(hbFontData->m_paint, glyph, &advance, 0); - return advance; -} - -static hb_bool_t harfbuzzGetGlyphHorizontalOrigin(hb_font_t* hbFont, void* fontData, hb_codepoint_t glyph, hb_position_t* x, hb_position_t* y, void* userData) -{ - // Just return true, following the way that Harfbuzz-FreeType - // implementation does. - return true; -} - -static hb_bool_t harfbuzzGetGlyphExtents(hb_font_t* hbFont, void* fontData, hb_codepoint_t glyph, hb_glyph_extents_t* extents, void* userData) -{ - HarfBuzzFontData* hbFontData = reinterpret_cast<HarfBuzzFontData*>(fontData); - - SkiaGetGlyphWidthAndExtents(hbFontData->m_paint, glyph, 0, extents); - return true; -} - -static hb_font_funcs_t* harfbuzzSkiaGetFontFuncs() -{ - static hb_font_funcs_t* harfbuzzSkiaFontFuncs = 0; - - // We don't set callback functions which we can't support. - // Harfbuzz will use the fallback implementation if they aren't set. - if (!harfbuzzSkiaFontFuncs) { - harfbuzzSkiaFontFuncs = hb_font_funcs_create(); - hb_font_funcs_set_glyph_func(harfbuzzSkiaFontFuncs, harfbuzzGetGlyph, 0, 0); - hb_font_funcs_set_glyph_h_advance_func(harfbuzzSkiaFontFuncs, harfbuzzGetGlyphHorizontalAdvance, 0, 0); - hb_font_funcs_set_glyph_h_origin_func(harfbuzzSkiaFontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0); - hb_font_funcs_set_glyph_extents_func(harfbuzzSkiaFontFuncs, harfbuzzGetGlyphExtents, 0, 0); - hb_font_funcs_make_immutable(harfbuzzSkiaFontFuncs); - } - return harfbuzzSkiaFontFuncs; -} - -hb_blob_t* harfbuzzSkiaReferenceTable(hb_face_t* face, hb_tag_t tag, void* userData) -{ - SkTypeface* typeface = reinterpret_cast<SkTypeface*>(userData); - - const size_t tableSize = typeface->getTableSize(tag); - if (!tableSize) - return 0; - - char* buffer = reinterpret_cast<char*>(malloc(tableSize)); - if (!buffer) - return 0; - size_t actualSize = typeface->getTableData(tag, 0, tableSize, buffer); - if (tableSize != actualSize) { - free(buffer); - return 0; - } - - return hb_blob_create(const_cast<char*>(buffer), tableSize, - HB_MEMORY_MODE_WRITABLE, buffer, free); -} - -static void destroyHarfBuzzFontData(void* data) { - delete (HarfBuzzFontData*)data; -} - -hb_font_t* createFont(hb_face_t* face, SkPaint* paint, float sizeX, float sizeY) { - hb_font_t* font = hb_font_create(face); - - // Note: this needs to be reworked when we do subpixels - int x_ppem = floor(sizeX + 0.5); - int y_ppem = floor(sizeY + 0.5); - hb_font_set_ppem(font, x_ppem, y_ppem); - hb_font_set_scale(font, HBFloatToFixed(sizeX), HBFloatToFixed(sizeY)); - - HarfBuzzFontData* data = new HarfBuzzFontData(paint); - hb_font_set_funcs(font, harfbuzzSkiaGetFontFuncs(), data, destroyHarfBuzzFontData); - - return font; -} - -} // namespace android diff --git a/core/jni/android/graphics/text/MeasuredText.cpp b/core/jni/android/graphics/text/MeasuredText.cpp index 0bfadb407a93..d7d96fbf3956 100644 --- a/core/jni/android/graphics/text/MeasuredText.cpp +++ b/core/jni/android/graphics/text/MeasuredText.cpp @@ -84,14 +84,14 @@ static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong // Regular JNI static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, - jcharArray javaText, jboolean computeHyphenation, - jboolean computeLayout) { + jlong hintPtr, jcharArray javaText, jboolean computeHyphenation, + jboolean computeLayout) { ScopedCharArrayRO text(env, javaText); const minikin::U16StringPiece textBuffer(text.get(), text.size()); // Pass the ownership to Java. - return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation, - computeLayout).release()); + return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation, computeLayout, + toMeasuredParagraph(hintPtr)).release()); } // Regular JNI @@ -147,7 +147,7 @@ static const JNINativeMethod gMTBuilderMethods[] = { {"nInitBuilder", "()J", (void*) nInitBuilder}, {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun}, - {"nBuildMeasuredText", "(J[CZZ)J", (void*) nBuildMeasuredText}, + {"nBuildMeasuredText", "(JJ[CZZ)J", (void*) nBuildMeasuredText}, {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, }; diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp index 98bc735e1fae..8a280343e731 100644 --- a/core/jni/android_hardware_SoundTrigger.cpp +++ b/core/jni/android_hardware_SoundTrigger.cpp @@ -225,7 +225,8 @@ void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognitio gAudioFormatCstor, audioFormatFromNative(event->audio_config.format), event->audio_config.sample_rate, - inChannelMaskFromNative(event->audio_config.channel_mask)); + inChannelMaskFromNative(event->audio_config.channel_mask), + (jint)0 /* channelIndexMask */); } if (event->type == SOUND_MODEL_TYPE_KEYPHRASE) { diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index adab8e2bae39..283eb035c6f7 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2061,6 +2061,12 @@ android_media_AudioSystem_setA11yServicesUids(JNIEnv *env, jobject thiz, jintArr return (jint)nativeToJavaStatus(status); } +static jboolean +android_media_AudioSystem_isHapticPlaybackSupported(JNIEnv *env, jobject thiz) +{ + return AudioSystem::isHapticPlaybackSupported(); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { @@ -2123,6 +2129,7 @@ static const JNINativeMethod gMethods[] = { {"setSurroundFormatEnabled", "(IZ)I", (void *)android_media_AudioSystem_setSurroundFormatEnabled}, {"setAssistantUid", "(I)I", (void *)android_media_AudioSystem_setAssistantUid}, {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids}, + {"isHapticPlaybackSupported", "()Z", (void *)android_media_AudioSystem_isHapticPlaybackSupported}, }; static const JNINativeMethod gEventHandlerMethods[] = { diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index d927972cf8d3..516093e42691 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -23,8 +23,6 @@ #include <nativehelper/JniConstants.h> #include "core_jni_helpers.h" -#include <nativehelper/ScopedBytes.h> - #include <utils/Log.h> #include <media/AudioSystem.h> #include <media/AudioTrack.h> @@ -481,6 +479,24 @@ native_init_failure: } // ---------------------------------------------------------------------------- +static jboolean +android_media_AudioTrack_is_direct_output_supported(JNIEnv *env, jobject thiz, + jint encoding, jint sampleRate, + jint channelMask, jint channelIndexMask, + jint contentType, jint usage, jint flags) { + audio_config_base_t config = {}; + audio_attributes_t attributes = {}; + config.format = static_cast<audio_format_t>(audioFormatToNative(encoding)); + config.sample_rate = static_cast<uint32_t>(sampleRate); + config.channel_mask = nativeChannelMaskFromJavaChannelMasks(channelMask, channelIndexMask); + attributes.content_type = static_cast<audio_content_type_t>(contentType); + attributes.usage = static_cast<audio_usage_t>(usage); + attributes.flags = static_cast<audio_flags_mask_t>(flags); + // ignore source and tags attributes as they don't affect querying whether output is supported + return AudioTrack::isDirectOutputSupported(config, attributes); +} + +// ---------------------------------------------------------------------------- static void android_media_AudioTrack_start(JNIEnv *env, jobject thiz) { @@ -712,7 +728,7 @@ static jint android_media_AudioTrack_writeArray(JNIEnv *env, jobject thiz, // ---------------------------------------------------------------------------- static jint android_media_AudioTrack_write_native_bytes(JNIEnv *env, jobject thiz, - jbyteArray javaBytes, jint byteOffset, jint sizeInBytes, + jobject javaByteBuffer, jint byteOffset, jint sizeInBytes, jint javaAudioFormat, jboolean isWriteBlocking) { //ALOGV("android_media_AudioTrack_write_native_bytes(offset=%d, sizeInBytes=%d) called", // offsetInBytes, sizeInBytes); @@ -723,13 +739,14 @@ static jint android_media_AudioTrack_write_native_bytes(JNIEnv *env, jobject th return (jint)AUDIO_JAVA_INVALID_OPERATION; } - ScopedBytesRO bytes(env, javaBytes); - if (bytes.get() == NULL) { + const jbyte* bytes = + reinterpret_cast<const jbyte*>(env->GetDirectBufferAddress(javaByteBuffer)); + if (bytes == NULL) { ALOGE("Error retrieving source of audio data to play, can't play"); return (jint)AUDIO_JAVA_BAD_VALUE; } - jint written = writeToTrack(lpTrack, javaAudioFormat, bytes.get(), byteOffset, + jint written = writeToTrack(lpTrack, javaAudioFormat, bytes, byteOffset, sizeInBytes, isWriteBlocking == JNI_TRUE /* blocking */); return written; @@ -1298,6 +1315,9 @@ static jint android_media_AudioTrack_get_port_id(JNIEnv *env, jobject thiz) { // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { // name, signature, funcPtr + {"native_is_direct_output_supported", + "(IIIIIII)Z", + (void *)android_media_AudioTrack_is_direct_output_supported}, {"native_start", "()V", (void *)android_media_AudioTrack_start}, {"native_stop", "()V", (void *)android_media_AudioTrack_stop}, {"native_pause", "()V", (void *)android_media_AudioTrack_pause}, @@ -1308,7 +1328,7 @@ static const JNINativeMethod gMethods[] = { {"native_release", "()V", (void *)android_media_AudioTrack_release}, {"native_write_byte", "([BIIIZ)I",(void *)android_media_AudioTrack_writeArray<jbyteArray>}, {"native_write_native_bytes", - "(Ljava/lang/Object;IIIZ)I", + "(Ljava/nio/ByteBuffer;IIIZ)I", (void *)android_media_AudioTrack_write_native_bytes}, {"native_write_short", "([SIIIZ)I",(void *)android_media_AudioTrack_writeArray<jshortArray>}, {"native_write_float", "([FIIIZ)I",(void *)android_media_AudioTrack_writeArray<jfloatArray>}, diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index b1a9866efde7..06625b36f162 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -32,15 +32,16 @@ void setDriverPath(JNIEnv* env, jobject clazz, jstring path) { android::GraphicsEnv::getInstance().setDriverPath(pathChars.c_str()); } -void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName, jboolean devOptIn, +void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName, jstring devOptIn, jobject rulesFd, jlong rulesOffset, jlong rulesLength) { ScopedUtfChars pathChars(env, path); ScopedUtfChars appNameChars(env, appName); + ScopedUtfChars devOptInChars(env, devOptIn); int rulesFd_native = jniGetFDFromFileDescriptor(env, rulesFd); android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(), - devOptIn, rulesFd_native, rulesOffset, rulesLength); + devOptInChars.c_str(), rulesFd_native, rulesOffset, rulesLength); } void setLayerPaths_native(JNIEnv* env, jobject clazz, jobject classLoader, jstring layerPaths) { @@ -67,7 +68,7 @@ void setDebugLayersGLES_native(JNIEnv* env, jobject clazz, jstring layers) { const JNINativeMethod g_methods[] = { { "getCanLoadSystemLibraries", "()I", reinterpret_cast<void*>(getCanLoadSystemLibraries_native) }, { "setDriverPath", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDriverPath) }, - { "setAngleInfo", "(Ljava/lang/String;Ljava/lang/String;ZLjava/io/FileDescriptor;JJ)V", reinterpret_cast<void*>(setAngleInfo_native) }, + { "setAngleInfo", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/FileDescriptor;JJ)V", reinterpret_cast<void*>(setAngleInfo_native) }, { "setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", reinterpret_cast<void*>(setLayerPaths_native) }, { "setDebugLayers", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayers_native) }, { "setDebugLayersGLES", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayersGLES_native) }, diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index ea6e0178bd9c..c745c160e143 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -37,6 +37,7 @@ #include <stdio.h> #include <system/graphics.h> #include <ui/DisplayInfo.h> +#include <ui/DisplayedFrameStats.h> #include <ui/FrameStats.h> #include <ui/GraphicTypes.h> #include <ui/HdrCapabilities.h> @@ -97,6 +98,16 @@ static struct { jmethodID builder; } gGraphicBufferClassInfo; +static struct { + jclass clazz; + jmethodID ctor; +} gDisplayedContentSampleClassInfo; + +static struct { + jclass clazz; + jmethodID ctor; +} gDisplayedContentSamplingAttributesClassInfo; + // ---------------------------------------------------------------------------- static jlong nativeCreateTransaction(JNIEnv* env, jclass clazz) { @@ -398,6 +409,73 @@ static jobject nativeGetBuiltInDisplay(JNIEnv* env, jclass clazz, jint id) { return javaObjectForIBinder(env, token); } +static jobject nativeGetDisplayedContentSamplingAttributes(JNIEnv* env, jclass clazz, + jobject tokenObj) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + + ui::PixelFormat format; + ui::Dataspace dataspace; + uint8_t componentMask; + status_t err = SurfaceComposerClient::getDisplayedContentSamplingAttributes( + token, &format, &dataspace, &componentMask); + if (err != OK) { + return nullptr; + } + return env->NewObject(gDisplayedContentSamplingAttributesClassInfo.clazz, + gDisplayedContentSamplingAttributesClassInfo.ctor, + format, dataspace, componentMask); +} + +static jboolean nativeSetDisplayedContentSamplingEnabled(JNIEnv* env, jclass clazz, + jobject tokenObj, jboolean enable, jint componentMask, jint maxFrames) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + return SurfaceComposerClient::setDisplayContentSamplingEnabled( + token, enable, componentMask, maxFrames); +} + +static jobject nativeGetDisplayedContentSample(JNIEnv* env, jclass clazz, jobject tokenObj, + jlong maxFrames, jlong timestamp) { + sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); + + DisplayedFrameStats stats; + status_t err = SurfaceComposerClient::getDisplayedContentSample( + token, maxFrames, timestamp, &stats); + if (err != OK) { + return nullptr; + } + + jlongArray histogramComponent0 = env->NewLongArray(stats.component_0_sample.size()); + jlongArray histogramComponent1 = env->NewLongArray(stats.component_1_sample.size()); + jlongArray histogramComponent2 = env->NewLongArray(stats.component_2_sample.size()); + jlongArray histogramComponent3 = env->NewLongArray(stats.component_3_sample.size()); + if ((histogramComponent0 == nullptr) || + (histogramComponent1 == nullptr) || + (histogramComponent2 == nullptr) || + (histogramComponent3 == nullptr)) { + return JNI_FALSE; + } + + env->SetLongArrayRegion(histogramComponent0, 0, + stats.component_0_sample.size(), + reinterpret_cast<jlong*>(stats.component_0_sample.data())); + env->SetLongArrayRegion(histogramComponent1, 0, + stats.component_1_sample.size(), + reinterpret_cast<jlong*>(stats.component_1_sample.data())); + env->SetLongArrayRegion(histogramComponent2, 0, + stats.component_2_sample.size(), + reinterpret_cast<jlong*>(stats.component_2_sample.data())); + env->SetLongArrayRegion(histogramComponent3, 0, + stats.component_3_sample.size(), + reinterpret_cast<jlong*>(stats.component_3_sample.data())); + return env->NewObject(gDisplayedContentSampleClassInfo.clazz, + gDisplayedContentSampleClassInfo.ctor, + stats.numFrames, + histogramComponent0, + histogramComponent1, + histogramComponent2, + histogramComponent3); +} + static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure) { ScopedUtfChars name(env, nameObj); @@ -955,6 +1033,14 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeCaptureLayers }, {"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V", (void*)nativeSetInputWindowInfo }, + {"nativeGetDisplayedContentSamplingAttributes", + "(Landroid/os/IBinder;)Landroid/hardware/display/DisplayedContentSamplingAttributes;", + (void*)nativeGetDisplayedContentSamplingAttributes }, + {"nativeSetDisplayedContentSamplingEnabled", "(Landroid/os/IBinder;ZII)Z", + (void*)nativeSetDisplayedContentSamplingEnabled }, + {"nativeGetDisplayedContentSample", + "(Landroid/os/IBinder;JJ)Landroid/hardware/display/DisplayedContentSample;", + (void*)nativeGetDisplayedContentSample }, }; int register_android_view_SurfaceControl(JNIEnv* env) @@ -1009,6 +1095,18 @@ int register_android_view_SurfaceControl(JNIEnv* env) gGraphicBufferClassInfo.builder = GetStaticMethodIDOrDie(env, graphicsBufferClazz, "createFromExisting", "(IIIIJ)Landroid/graphics/GraphicBuffer;"); + jclass displayedContentSampleClazz = FindClassOrDie(env, + "android/hardware/display/DisplayedContentSample"); + gDisplayedContentSampleClassInfo.clazz = MakeGlobalRefOrDie(env, displayedContentSampleClazz); + gDisplayedContentSampleClassInfo.ctor = GetMethodIDOrDie(env, + displayedContentSampleClazz, "<init>", "(J[J[J[J[J)V"); + + jclass displayedContentSamplingAttributesClazz = FindClassOrDie(env, + "android/hardware/display/DisplayedContentSamplingAttributes"); + gDisplayedContentSamplingAttributesClassInfo.clazz = MakeGlobalRefOrDie(env, + displayedContentSamplingAttributesClazz); + gDisplayedContentSamplingAttributesClassInfo.ctor = GetMethodIDOrDie(env, + displayedContentSamplingAttributesClazz, "<init>", "(III)V"); return err; } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 02867304af36..4aa88e7558cd 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -276,15 +276,17 @@ static void EnableDebugger() { } } - // We don't want core dumps, though, so set the soft limit on core dump size - // to 0 without changing the hard limit. - rlimit rl; - if (getrlimit(RLIMIT_CORE, &rl) == -1) { - ALOGE("getrlimit(RLIMIT_CORE) failed"); - } else { - rl.rlim_cur = 0; - if (setrlimit(RLIMIT_CORE, &rl) == -1) { - ALOGE("setrlimit(RLIMIT_CORE) failed"); + // Set the core dump size to zero unless wanted (see also coredump_setup in build/envsetup.sh). + if (!GetBoolProperty("persist.zygote.core_dump", false)) { + // Set the soft limit on core dump size to 0 without changing the hard limit. + rlimit rl; + if (getrlimit(RLIMIT_CORE, &rl) == -1) { + ALOGE("getrlimit(RLIMIT_CORE) failed"); + } else { + rl.rlim_cur = 0; + if (setrlimit(RLIMIT_CORE, &rl) == -1) { + ALOGE("setrlimit(RLIMIT_CORE) failed"); + } } } } diff --git a/core/proto/android/app/job/enums.proto b/core/proto/android/app/job/enums.proto index 2290b2f8974a..bba832880669 100644 --- a/core/proto/android/app/job/enums.proto +++ b/core/proto/android/app/job/enums.proto @@ -30,4 +30,5 @@ enum StopReasonEnum { STOP_REASON_PREEMPT = 2; STOP_REASON_TIMEOUT = 3; STOP_REASON_DEVICE_IDLE = 4; + STOP_REASON_DEVICE_THERMAL = 5; } diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index 8e4eb0018999..514f306953a5 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -73,5 +73,14 @@ enum PageId { // OPEN: Settings > Privacy TOP_LEVEL_PRIVACY = 1587; + + // OPEN: Settings > Developer options > Disable > Info dialog + DIALOG_DISABLE_DEVELOPMENT_OPTIONS = 1591; + + // OPEN: WifiDppConfiguratorActivity (android.settings.WIFI_DPP_CONFIGURATOR_XXX action intents) + SETTINGS_WIFI_DPP_CONFIGURATOR = 1595; + + // OPEN: WifiDppEnrolleeActivity (android.settings.WIFI_DPP_ENROLLEE_XXX action intents) + SETTINGS_WIFI_DPP_ENROLLEE = 1596; } diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 0b9e347ce5d1..a5350c9dc1b8 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -34,6 +34,7 @@ import "frameworks/base/core/proto/android/server/alarmmanagerservice.proto"; import "frameworks/base/core/proto/android/server/fingerprint.proto"; import "frameworks/base/core/proto/android/server/jobscheduler.proto"; import "frameworks/base/core/proto/android/server/powermanagerservice.proto"; +import "frameworks/base/core/proto/android/server/rolemanagerservice.proto"; import "frameworks/base/core/proto/android/server/windowmanagerservice.proto"; import "frameworks/base/core/proto/android/service/appwidget.proto"; import "frameworks/base/core/proto/android/service/battery.proto"; @@ -308,6 +309,11 @@ message IncidentProto { (section).userdebug_and_eng_only = true ]; + optional com.android.server.role.RoleManagerServiceDumpProto role = 3024 [ + (section).type = SECTION_DUMPSYS, + (section).args = "role --proto" + ]; + // Reserved for OEMs. extensions 50000 to 100000; } diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index 4c1fc5c89b0d..11bd43b11977 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -109,7 +109,15 @@ message GlobalSettingsProto { } optional Autofill autofill = 140; - optional SettingProto backup_agent_timeout_parameters = 18; + reserved 18; // Used to be backup_agent_timeout_parameters + + message Backup { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + optional SettingProto backup_agent_timeout_parameters = 1; + optional SettingProto backup_multi_user_enabled = 2; + } + optional Backup backup = 146; message Battery { option (android.msg_privacy).dest = DEST_EXPLICIT; @@ -222,6 +230,13 @@ message GlobalSettingsProto { } optional Connectivity connectivity = 32; + message ContentCapture { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + optional SettingProto service_explicitly_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; + } + optional ContentCapture content_capture = 145; + optional SettingProto contact_metadata_sync_enabled = 33 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto contacts_database_wal_enabled = 34 [ (android.privacy).dest = DEST_AUTOMATIC ]; @@ -410,16 +425,20 @@ message GlobalSettingsProto { // Ordered GPU debug layer list for Vulkan // i.e. <layer1>:<layer2>:...:<layerN> optional SettingProto debug_layers = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; - // App will load ANGLE instead of native GLES drivers. - optional SettingProto angle_enabled_app = 3; + // ANGLE - Force all PKGs to use ANGLE, regardless of any other settings + optional SettingProto angle_gl_driver_all_angle = 3; + // ANGLE - List of PKGs that specify an OpenGL driver + optional SettingProto angle_gl_driver_selection_pkgs = 4; + // ANGLE - Corresponding OpenGL driver selection for the PKG + optional SettingProto angle_gl_driver_selection_values = 5; // App that can provide layer libraries. - optional SettingProto debug_layer_app = 4; + optional SettingProto debug_layer_app = 6; // Ordered GPU debug layer list for GLES // i.e. <layer1>:<layer2>:...:<layerN> - optional SettingProto debug_layers_gles = 5; + optional SettingProto debug_layers_gles = 7; // App opt in to load updated graphics driver instead of // native graphcis driver through developer options. - optional SettingProto updated_gfx_driver_dev_opt_in_app = 6; + optional SettingProto updated_gfx_driver_dev_opt_in_app = 8; } optional Gpu gpu = 59; @@ -628,6 +647,9 @@ message GlobalSettingsProto { // separated by commas. optional SettingProto snooze_options = 3 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto smart_replies_in_notifications_flags = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Configuration options for smart replies and smart actions in notifications. This is + // encoded as a key=value list separated by commas. + optional SettingProto smart_suggestions_in_notifications_flags = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Notification notification = 82; @@ -761,13 +783,6 @@ message GlobalSettingsProto { } optional SmartSelection smart_selection = 108; - message SmartSuggestions { - option (android.msg_privacy).dest = DEST_EXPLICIT; - - optional SettingProto service_explicitly_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; - } - optional SmartSuggestions smart_suggestions = 145; - message Sms { option (android.msg_privacy).dest = DEST_EXPLICIT; @@ -1000,5 +1015,5 @@ message GlobalSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 146; + // Next tag = 147; } diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 231caabe0335..c2bc7bf91be9 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -41,6 +41,7 @@ message JobSchedulerServiceDumpProto { optional int64 last_heartbeat_time_millis = 16; optional int64 next_heartbeat_time_millis = 17; optional bool in_parole = 18; + optional bool in_thermal = 19; repeated int32 started_users = 2; diff --git a/core/proto/android/server/rolemanagerservice.proto b/core/proto/android/server/rolemanagerservice.proto new file mode 100644 index 000000000000..3453a6649e02 --- /dev/null +++ b/core/proto/android/server/rolemanagerservice.proto @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018 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. + */ + +syntax = "proto2"; + +package com.android.server.role; + +option java_multiple_files = true; + +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + +message RoleManagerServiceDumpProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // List of per-user states for all users. + repeated RoleUserStateProto user_states = 1; +} + +message RoleUserStateProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // The user id of this state. + optional int32 user_id = 1; + + // The version of this state. + optional int32 version = 2; + + // The hash of packages for this state. + optional string packages_hash = 3; + + // The set of roles in this state. + repeated RoleProto roles = 4; +} + +message RoleProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // The name of this role, e.g. "android.app.role.DIALER". + optional string name = 1; + + // The package names of the holders of this role. + repeated string holders = 2; +} diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto index 3d60a86d86c9..528c1a4134f1 100644 --- a/core/proto/android/server/usagestatsservice.proto +++ b/core/proto/android/server/usagestatsservice.proto @@ -54,6 +54,9 @@ message IntervalStatsProto { // Time attributes stored as an offset of the IntervalStats's beginTime. optional int64 last_time_service_used_ms = 8; optional int64 total_time_service_used_ms = 9; + // Time attributes stored as an offset of the IntervalStats's beginTime. + optional int64 last_time_visible_ms = 10; + optional int64 total_time_visible_ms = 11; } // Stores the relevant information an IntervalStats will have about a Configuration @@ -82,6 +85,9 @@ message IntervalStatsProto { optional string notification_channel = 12; // notification_channel_index contains the index + 1 of the channel name in the string pool optional int32 notification_channel_index = 13; + // If class field is an Activity, instance_id is a unique id of the + // Activity object. + optional int32 instance_id = 14; } // The following fields contain supplemental data used to build IntervalStats, such as a string diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto index ed040f43253a..f7dcee282a57 100644 --- a/core/proto/android/service/usb.proto +++ b/core/proto/android/service/usb.proto @@ -206,6 +206,8 @@ message UsbPortInfoProto { optional bool can_change_mode = 3; optional bool can_change_power_role = 4; optional bool can_change_data_role = 5; + optional int64 connected_at_millis = 6; + optional int64 last_connect_duration_millis = 7; } message UsbPortProto { diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto index 8fbea12a8fb9..c7a6b68fbc00 100644 --- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto +++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto @@ -44,14 +44,14 @@ enum EventId { SET_PERMISSION_GRANT_STATE = 19; INSTALL_KEY_PAIR = 20; INSTALL_CA_CERT = 21; - ON_CHOOSE_KEY_ALIAS = 22; + CHOOSE_PRIVATE_KEY_ALIAS = 22; REMOVE_KEY_PAIR = 23; UNINSTALL_CA_CERTS = 24; SET_CERT_INSTALLER_PACKAGE = 25; SET_ALWAYS_ON_VPN_PACKAGE = 26; SET_PERMITTED_INPUT_METHODS = 27; SET_PERMITTED_ACCESSIBILITY_SERVICES = 28; - SET_SCREEN_CAPTURE_DISABLE = 29; + SET_SCREEN_CAPTURE_DISABLED = 29; SET_CAMERA_DISABLED = 30; QUERY_SUMMARY_FOR_USER = 31; QUERY_SUMMARY = 32; @@ -64,16 +64,16 @@ enum EventId { SET_ORGANIZATION_COLOR = 39; SET_PROFILE_NAME = 40; SET_USER_ICON = 41; - SET_DEVICE_OWNER_LOCKSCREEN_INFO = 42; + SET_DEVICE_OWNER_LOCK_SCREEN_INFO = 42; SET_SHORT_SUPPORT_MESSAGE = 43; SET_LONG_SUPPORT_MESSAGE = 44; SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED = 45; - SET_CROSS_PROFILE_CALLER_DISABLED = 46; + SET_CROSS_PROFILE_CALLER_ID_DISABLED = 46; SET_BLUETOOTH_CONTACT_SHARING_DISABLED = 47; ADD_CROSS_PROFILE_INTENT_FILTER = 48; ADD_CROSS_PROFILE_WIDGET_PROVIDER = 49; SET_SYSTEM_UPDATE_POLICY = 50; - SET_LOCKTASK_PACKAGES = 51; + SET_LOCKTASK_MODE_ENABLED = 51; ADD_PERSISTENT_PREFERRED_ACTIVITY = 52; REQUEST_BUGREPORT = 53; GET_WIFI_MAC_ADDRESS = 54; @@ -99,13 +99,13 @@ enum EventId { INSTALL_SYSTEM_UPDATE_ERROR = 74; IS_MANAGED_KIOSK = 75; IS_UNATTENDED_MANAGED_KIOSK = 76; - PROVISIONING_TO_COMP = 77; - PROVISIONING_FORCED_DO = 78; + PROVISIONING_MANAGED_PROFILE_ON_FULLY_MANAGED_DEVICE = 77; + PROVISIONING_PERSISTENT_DEVICE_OWNER = 78; // existing Tron logs to be migrated to WestWorld PROVISIONING_ENTRY_POINT_NFC = 79; PROVISIONING_ENTRY_POINT_QR_CODE = 80; - PROVISIONING_ENTRY_POINT_ZERO_TOUCH = 81; + PROVISIONING_ENTRY_POINT_CLOUD_ENROLLMENT = 81; PROVISIONING_ENTRY_POINT_ADB = 82; PROVISIONING_ENTRY_POINT_TRUSTED_SOURCE = 83; PROVISIONING_DPC_PACKAGE_NAME = 84; @@ -134,4 +134,11 @@ enum EventId { PROVISIONING_TERMS_ACTIVITY_TIME_MS = 107; PROVISIONING_TERMS_COUNT = 108; PROVISIONING_TERMS_READ = 109; + + SEPARATE_PROFILE_CHALLENGE_CHANGED = 110; + SET_GLOBAL_SETTING = 111; + PM_IS_INSTALLER_DEVICE_OWNER_OR_AFFILIATED_PROFILE_OWNER = 112; + PM_UNINSTALL = 113; + WIFI_SERVICE_ADD_NETWORK_SUGGESTIONS = 114; + WIFI_SERVICE_ADD_OR_UPDATE_NETWORK = 115; } diff --git a/core/proto/android/stats/docsui/docsui_enums.proto b/core/proto/android/stats/docsui/docsui_enums.proto new file mode 100644 index 000000000000..6cb606ad1cab --- /dev/null +++ b/core/proto/android/stats/docsui/docsui_enums.proto @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2018 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. + */ + +syntax = "proto2"; +package android.stats.docsui; +option java_multiple_files = true; + +enum LaunchAction { + UNKNOWN = 0; + OPEN = 1; + CREATE = 2; + GET_CONTENT = 3; + OPEN_TREE = 4; + PICK_COPY_DEST = 5; + BROWSE = 6; + OTHER = 7; +} + +enum MimeType { + MIME_UNKNOWN = 0; + MIME_NONE = 1; + MIME_ANY = 2; + MIME_AUDIO = 3; + MIME_IMAGE = 4; + MIME_MESSAGE = 5; + MIME_MULTIPART = 6; + MIME_TEXT = 7; + MIME_VIDEO = 8; + MIME_OTHER = 9; +} + +enum Root { + ROOT_UNKNOWN = 0; + ROOT_NONE = 1; + ROOT_OTHER_DOCS_PROVIDER = 2; + ROOT_AUDIO = 3; + ROOT_DEVICE_STORAGE = 4; + ROOT_DOWNLOADS = 5; + ROOT_HOME = 6; + ROOT_IMAGES = 7; + ROOT_RECENTS = 8; + ROOT_VIDEOS = 9; + ROOT_MTP = 10; + ROOT_THIRD_PARTY_APP = 11; +} + +enum ContextScope { + SCOPE_UNKNOWN = 0; + SCOPE_FILES = 1; + SCOPE_PICKER = 2; +} + +enum Provider { + PROVIDER_UNKNOWN = 0; + PROVIDER_SYSTEM = 1; + PROVIDER_EXTERNAL = 2; +} + +enum FileOperation { + OP_UNKNOWN = 0; + OP_OTHER = 1; + OP_COPY = 2; + OP_COPY_INTRA_PROVIDER = 3; + OP_COPY_SYSTEM_PROVIDER = 4; + OP_COPY_EXTERNAL_PROVIDER = 5; + OP_MOVE = 6; + OP_MOVE_INTRA_PROVIDER = 7; + OP_MOVE_SYSTEM_PROVIDER = 8; + OP_MOVE_EXTERNAL_PROVIDER = 9; + OP_DELETE = 10; + OP_RENAME = 11; + OP_CREATE_DIR = 12; + OP_OTHER_ERROR = 13; + OP_DELETE_ERROR = 14; + OP_MOVE_ERROR = 15; + OP_COPY_ERROR = 16; + OP_RENAME_ERROR = 17; + OP_CREATE_DIR_ERROR = 18; + OP_COMPRESS_INTRA_PROVIDER = 19; + OP_COMPRESS_SYSTEM_PROVIDER = 20; + OP_COMPRESS_EXTERNAL_PROVIDER = 21; + OP_EXTRACT_INTRA_PROVIDER = 22; + OP_EXTRACT_SYSTEM_PROVIDER = 23; + OP_EXTRACT_EXTERNAL_PROVIDER = 24; + OP_COMPRESS_ERROR = 25; + OP_EXTRACT_ERROR = 26; +} + +enum SubFileOperation { + SUB_OP_UNKNOWN = 0; + SUB_OP_QUERY_DOC = 1; + SUB_OP_QUERY_CHILD = 2; + SUB_OP_OPEN_FILE = 3; + SUB_OP_READ_FILE = 4; + SUB_OP_CREATE_DOC = 5; + SUB_OP_WRITE_FILE = 6; + SUB_OP_DELETE_DOC = 7; + SUB_OP_OBTAIN_STREAM_TYPE = 8; + SUB_OP_QUICK_MOVE = 9; + SUB_OP_QUICK_COPY = 10; +} + +enum CopyMoveOpMode { + MODE_UNKNOWN = 0; + MODE_PROVIDER = 1; + MODE_CONVERTED = 2; + MODE_CONVENTIONAL = 3; +} + +enum Authority { + AUTH_UNKNOWN = 0; + AUTH_OTHER = 1; + AUTH_MEDIA = 2; + AUTH_STORAGE_INTERNAL = 3; + AUTH_STORAGE_EXTERNAL = 4; + AUTH_DOWNLOADS = 5; + AUTH_MTP = 6; +} + +enum UserAction { + ACTION_UNKNOWN = 0; + ACTION_OTHER = 1; + ACTION_GRID = 2; + ACTION_LIST = 3; + ACTION_SORT_NAME = 4; + ACTION_SORT_DATE = 5; + ACTION_SORT_SIZE = 6; + ACTION_SORT_TYPE = 7; + ACTION_SEARCH = 8; + ACTION_SHOW_SIZE = 9; + ACTION_HIDE_SIZE = 10; + ACTION_SETTINGS = 11; + ACTION_COPY_TO = 12; + ACTION_MOVE_TO = 13; + ACTION_DELETE = 14; + ACTION_RENAME = 15; + ACTION_CREATE_DIR = 16; + ACTION_SELECT_ALL = 17; + ACTION_SHARE = 18; + ACTION_OPEN = 19; + ACTION_SHOW_ADVANCED = 20; + ACTION_HIDE_ADVANCED = 21; + ACTION_NEW_WINDOW = 22; + ACTION_PASTE_CLIPBOARD = 23; + ACTION_COPY_CLIPBOARD = 24; + ACTION_DRAG_N_DROP = 25; + ACTION_DRAG_N_DROP_MULTI_WINDOW = 26; + ACTION_CUT_CLIPBOARD = 27; + ACTION_COMPRESS = 28; + ACTION_EXTRACT_TO = 29; + ACTION_VIEW_IN_APPLICATION = 30; + ACTION_INSPECTOR = 31; +} + +enum InvalidScopedAccess { + SCOPED_DIR_ACCESS_UNKNOWN = 0; + SCOPED_DIR_ACCESS_INVALID_ARGUMENTS = 1; + SCOPED_DIR_ACCESS_INVALID_DIRECTORY = 2; + SCOPED_DIR_ACCESS_ERROR = 3; + SCOPED_DIR_ACCESS_DEPRECATED = 4; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 3018614fee61..77efbecc57ce 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -897,7 +897,7 @@ android:protectionLevel="dangerous" android:usageInfoRequired="true" /> - <!-- @hide @SystemApi + <!-- @hide @SystemApi @TestApi Allows an application to modify OBB files visible to other apps. --> <permission android:name="android.permission.WRITE_OBB" android:protectionLevel="signature|privileged" /> @@ -1120,6 +1120,18 @@ android:description="@string/permdesc_manageOwnCalls" android:protectionLevel="normal" /> + <!--Allows an app which implements the + {@link InCallService} API to be eligible to be enabled as a calling companion app. This + means that the Telecom framework will bind to the app's InCallService implementation when + there are calls active. The app can use the InCallService API to view information about + calls on the system and control these calls. + <p>Protection level: normal + --> + <permission android:name="android.permission.CALL_COMPANION_APP" + android:label="@string/permlab_callCompanionApp" + android:description="@string/permdesc_callCompanionApp" + android:protectionLevel="normal" /> + <!-- Allows a calling app to continue a call which was started in another app. An example is a video calling app that wants to continue a voice call on the user's mobile network.<p> When the handover of a call from one app to another takes place, there are two devices @@ -1368,7 +1380,7 @@ <!-- ================================== --> <eat-comment /> - <!-- @SystemApi Allows an application (Phone) to send a request to other applications + <!-- Allows an application (Phone) to send a request to other applications to handle the respond-via-message action during incoming calls. <p>Not for use by third-party applications. --> <permission android:name="android.permission.SEND_RESPOND_VIA_MESSAGE" @@ -1452,7 +1464,7 @@ android:description="@string/permdesc_accessLocationExtraCommands" android:protectionLevel="normal" /> - <!-- @SystemApi Allows an application to install a location provider into the Location Manager. + <!-- Allows an application to install a location provider into the Location Manager. <p>Not for use by third-party applications. --> <permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" android:protectionLevel="signature|privileged" /> @@ -1463,7 +1475,7 @@ <permission android:name="android.permission.HDMI_CEC" android:protectionLevel="signature|privileged|vendorPrivileged" /> - <!-- @SystemApi Allows an application to use location features in hardware, + <!-- Allows an application to use location features in hardware, such as the geofencing api. <p>Not for use by third-party applications. --> <permission android:name="android.permission.LOCATION_HARDWARE" @@ -1595,7 +1607,7 @@ @hide This should only be used by ManagedProvisioning app. --> <permission android:name="android.permission.NETWORK_MANAGED_PROVISIONING" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature" /> <!-- #SystemApi @hide Allows applications to access information about LoWPAN interfaces. <p>Not for use by third-party applications. --> @@ -1648,7 +1660,7 @@ @hide --> <permission android:name="android.permission.SUSPEND_APPS" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|wellbeing" /> <!-- Allows applications to discover and pair bluetooth devices. <p>Protection level: normal @@ -1658,7 +1670,7 @@ android:label="@string/permlab_bluetoothAdmin" android:protectionLevel="normal" /> - <!-- @SystemApi Allows applications to pair bluetooth devices without user interaction, and to + <!-- Allows applications to pair bluetooth devices without user interaction, and to allow or disallow phonebook access or message access. This is not available to third party applications. --> <permission android:name="android.permission.BLUETOOTH_PRIVILEGED" @@ -1749,7 +1761,7 @@ android:usageInfoRequired="true" /> <uses-permission android:name="android.permission.GET_ACCOUNTS"/> - <!-- @SystemApi Allows applications to call into AccountAuthenticators. + <!-- Allows applications to call into AccountAuthenticators. <p>Not for use by third-party applications. --> <permission android:name="android.permission.ACCOUNT_MANAGER" android:protectionLevel="signature" /> @@ -1928,7 +1940,7 @@ <!-- =========================================== --> <eat-comment /> - <!-- @SystemApi Allows modification of the telephony state - power on, mmi, etc. + <!-- Allows modification of the telephony state - power on, mmi, etc. Does not include placing calls. <p>Not for use by third-party applications. --> <permission android:name="android.permission.MODIFY_PHONE_STATE" @@ -2087,17 +2099,15 @@ <p>This permission should <em>only</em> be requested by the platform document management app. This permission cannot be granted to third-party apps. - <p>Protection level: signature --> <permission android:name="android.permission.MANAGE_DOCUMENTS" - android:protectionLevel="signature" /> + android:protectionLevel="signature|documenter" /> <!-- @hide Allows an application to cache content. <p>Not for use by third-party applications. - <p>Protection level: signature --> <permission android:name="android.permission.CACHE_CONTENT" - android:protectionLevel="signature" /> + android:protectionLevel="signature|documenter" /> <!-- @SystemApi @hide Allows an application to aggressively allocate disk space. @@ -2128,6 +2138,15 @@ android:label="@string/permlab_disableKeyguard" android:protectionLevel="normal" /> + <!-- Allows an application to get the screen lock complexity and prompt users to update the + screen lock to a certain complexity level. + <p>Protection level: normal + --> + <permission android:name="android.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY" + android:label="@string/permlab_getAndRequestScreenLockComplexity" + android:description="@string/permdesc_getAndRequestScreenLockComplexity" + android:protectionLevel="normal" /> + <!-- ================================== --> <!-- Permissions to access other installed applications --> <!-- ================================== --> @@ -2171,6 +2190,13 @@ <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" android:protectionLevel="signature|installer" /> + <!-- @SystemApi Allows an application to start an activity within its managed profile from + the personal profile. + This permission is not available to third party applications. + @hide --> + <permission android:name="android.permission.INTERACT_ACROSS_PROFILES" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi @hide Allows an application to call APIs that allow it to query and manage users on the device. This permission is not available to third party applications. --> @@ -2206,9 +2232,9 @@ android:description="@string/permdesc_reorderTasks" android:protectionLevel="normal" /> - <!-- @hide Allows an application to change to remove/kill tasks --> + <!-- @SystemApi @TestApi @hide Allows an application to change to remove/kill tasks --> <permission android:name="android.permission.REMOVE_TASKS" - android:protectionLevel="signature" /> + android:protectionLevel="signature|documenter" /> <!-- @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks --> <permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" @@ -2365,7 +2391,7 @@ <!-- ============================================ --> <eat-comment /> - <!-- @SystemApi Allows applications to set the system time. + <!-- Allows applications to set the system time. <p>Not for use by third-party applications. --> <permission android:name="android.permission.SET_TIME" android:protectionLevel="signature|privileged" /> @@ -2455,7 +2481,7 @@ <permission android:name="android.permission.SET_SCREEN_COMPATIBILITY" android:protectionLevel="signature" /> - <!-- @SystemApi @TestApi Allows an application to modify the current configuration, such + <!-- Allows an application to modify the current configuration, such as locale. --> <permission android:name="android.permission.CHANGE_CONFIGURATION" android:protectionLevel="signature|privileged|development" /> @@ -2477,7 +2503,7 @@ android:description="@string/permdesc_writeSettings" android:protectionLevel="signature|preinstalled|appop|pre23" /> - <!-- @SystemApi Allows an application to modify the Google service map. + <!-- Allows an application to modify the Google service map. <p>Not for use by third-party applications. --> <permission android:name="android.permission.WRITE_GSERVICES" android:protectionLevel="signature|privileged" /> @@ -2493,7 +2519,7 @@ <permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Modify the global animation scaling factor. + <!-- Modify the global animation scaling factor. <p>Not for use by third-party applications. --> <permission android:name="android.permission.SET_ANIMATION_SCALE" android:protectionLevel="signature|privileged|development" /> @@ -2548,12 +2574,12 @@ android:description="@string/permdesc_broadcastSticky" android:protectionLevel="normal" /> - <!-- @SystemApi Allows mounting and unmounting file systems for removable storage. + <!-- Allows mounting and unmounting file systems for removable storage. <p>Not for use by third-party applications.--> <permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Allows formatting file systems for removable storage. + <!-- Allows formatting file systems for removable storage. <p>Not for use by third-party applications. --> <permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS" android:protectionLevel="signature|privileged" /> @@ -2587,7 +2613,7 @@ <permission android:name="android.permission.ASEC_RENAME" android:protectionLevel="signature" /> - <!-- @SystemApi Allows applications to write the apn settings and read sensitive fields of + <!-- Allows applications to write the apn settings and read sensitive fields of an existing apn settings like user and password. <p>Not for use by third-party applications. --> <permission android:name="android.permission.WRITE_APN_SETTINGS" @@ -2688,40 +2714,40 @@ <!-- ========================================= --> <eat-comment /> - <!-- @SystemApi Allows an application to read or write the secure system settings. + <!-- Allows an application to read or write the secure system settings. <p>Not for use by third-party applications. --> <permission android:name="android.permission.WRITE_SECURE_SETTINGS" android:protectionLevel="signature|privileged|development" /> - <!-- @SystemApi Allows an application to retrieve state dump information from system services. + <!-- Allows an application to retrieve state dump information from system services. <p>Not for use by third-party applications. --> <permission android:name="android.permission.DUMP" android:protectionLevel="signature|privileged|development" /> - <!-- @SystemApi Allows an application to read the low-level system log files. + <!-- Allows an application to read the low-level system log files. <p>Not for use by third-party applications, because Log entries can contain the user's private information. --> <permission android:name="android.permission.READ_LOGS" android:protectionLevel="signature|privileged|development" /> - <!-- @SystemApi Configure an application for debugging. + <!-- Configure an application for debugging. <p>Not for use by third-party applications. --> <permission android:name="android.permission.SET_DEBUG_APP" android:protectionLevel="signature|privileged|development" /> - <!-- @SystemApi Allows an application to set the maximum number of (not needed) + <!-- Allows an application to set the maximum number of (not needed) application processes that can be running. <p>Not for use by third-party applications. --> <permission android:name="android.permission.SET_PROCESS_LIMIT" android:protectionLevel="signature|privileged|development" /> - <!-- @SystemApi Allows an application to control whether activities are immediately + <!-- Allows an application to control whether activities are immediately finished when put in the background. <p>Not for use by third-party applications. --> <permission android:name="android.permission.SET_ALWAYS_FINISH" android:protectionLevel="signature|privileged|development" /> - <!-- @SystemApi Allow an application to request that a signal be sent to all persistent processes. + <!-- Allow an application to request that a signal be sent to all persistent processes. <p>Not for use by third-party applications. --> <permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES" android:protectionLevel="signature|privileged|development" /> @@ -2731,7 +2757,7 @@ <!-- ==================================== --> <eat-comment /> - <!-- @SystemApi Allows access to the list of accounts in the Accounts Service. --> + <!-- Allows access to the list of accounts in the Accounts Service. --> <permission android:name="android.permission.GET_ACCOUNTS_PRIVILEGED" android:protectionLevel="signature|privileged" /> @@ -2740,12 +2766,12 @@ <permission android:name="android.permission.GET_PASSWORD" android:protectionLevel="signature" /> - <!-- @SystemApi Allows applications to RW to diagnostic resources. + <!-- Allows applications to RW to diagnostic resources. <p>Not for use by third-party applications. --> <permission android:name="android.permission.DIAGNOSTIC" android:protectionLevel="signature" /> - <!-- @SystemApi Allows an application to open, close, or disable the status bar + <!-- Allows an application to open, close, or disable the status bar and its icons. <p>Not for use by third-party applications. --> <permission android:name="android.permission.STATUS_BAR" @@ -2770,7 +2796,7 @@ <permission android:name="android.permission.FORCE_BACK" android:protectionLevel="signature" /> - <!-- @SystemApi Allows an application to update device statistics. + <!-- Allows an application to update device statistics. <p>Not for use by third-party applications. --> <permission android:name="android.permission.UPDATE_DEVICE_STATS" android:protectionLevel="signature|privileged" /> @@ -3047,12 +3073,20 @@ <permission android:name="android.permission.BIND_TEXTCLASSIFIER_SERVICE" android:protectionLevel="signature" /> - <!-- Must be required by a android.service.intelligence.SmartSuggestionsService, + <!-- Must be required by a android.service.contentcapture.ContentCaptureService, + to ensure that only the system can bind to it. + @SystemApi @hide This is not a third-party API (intended for OEMs and system apps). + <p>Protection level: signature + --> + <permission android:name="android.permission.BIND_CONTENT_CAPTURE_SERVICE" + android:protectionLevel="signature" /> + + <!-- Must be required by a android.service.autofill.augmented.AugmentedAutofillService, to ensure that only the system can bind to it. @SystemApi @hide This is not a third-party API (intended for OEMs and system apps). <p>Protection level: signature --> - <permission android:name="android.permission.BIND_SMART_SUGGESTIONS_SERVICE" + <permission android:name="android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE" android:protectionLevel="signature" /> <!-- Must be required by hotword enrollment application, @@ -3209,7 +3243,7 @@ android:description="@string/permdesc_requestDeletePackages" android:protectionLevel="normal" /> - <!-- @SystemApi Allows an application to install packages. + <!-- Allows an application to install packages. <p>Not for use by third-party applications. --> <permission android:name="android.permission.INSTALL_PACKAGES" android:protectionLevel="signature|privileged" /> @@ -3282,7 +3316,7 @@ <permission android:name="android.permission.FORCE_PERSISTABLE_URI_PERMISSIONS" android:protectionLevel="signature" /> - <!-- @SystemApi Old permission for deleting an app's cache files, no longer used, + <!-- Old permission for deleting an app's cache files, no longer used, but signals for us to quietly ignore calls instead of throwing an exception. --> <permission android:name="android.permission.DELETE_CACHE_FILES" android:protectionLevel="signature|privileged" /> @@ -3292,7 +3326,7 @@ <permission android:name="android.permission.INTERNAL_DELETE_CACHE_FILES" android:protectionLevel="signature" /> - <!-- @SystemApi Allows an application to delete packages. + <!-- Allows an application to delete packages. <p>Not for use by third-party applications. <p>Starting in {@link android.os.Build.VERSION_CODES#N}, user confirmation is requested when the application deleting the package is not the same application that installed the @@ -3305,7 +3339,7 @@ <permission android:name="android.permission.MOVE_PACKAGE" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Allows an application to change whether an application component (other than its own) is + <!-- Allows an application to change whether an application component (other than its own) is enabled or not. <p>Not for use by third-party applications. --> <permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" @@ -3336,6 +3370,11 @@ <permission android:name="android.permission.MANAGE_ROLE_HOLDERS" android:protectionLevel="signature|installer" /> + <!-- @SystemApi Allows an application to observe role holder changes. + @hide --> + <permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" + android:protectionLevel="signature|installer" /> + <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features. <p>Not for use by third-party applications. @hide @@ -3428,7 +3467,7 @@ android:protectionLevel="signature|privileged" /> <uses-permission android:name="android.permission.CONTROL_VPN" /> - <!-- @SystemApi Allows an application to capture audio output. + <!-- Allows an application to capture audio output. <p>Not for use by third-party applications.</p> --> <permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" android:protectionLevel="signature|privileged" /> @@ -3466,7 +3505,7 @@ <permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Allows an application to know what content is playing and control its playback. + <!-- Allows an application to know what content is playing and control its playback. <p>Not for use by third-party applications due to privacy of media consumption</p> --> <permission android:name="android.permission.MEDIA_CONTENT_CONTROL" android:protectionLevel="signature|privileged" /> @@ -3493,7 +3532,7 @@ <permission android:name="android.permission.BRICK" android:protectionLevel="signature" /> - <!-- @SystemApi Required to be able to reboot the device. + <!-- Required to be able to reboot the device. <p>Not for use by third-party applications. --> <permission android:name="android.permission.REBOOT" android:protectionLevel="signature|privileged" /> @@ -3554,11 +3593,11 @@ <permission android:name="android.permission.BROADCAST_NETWORK_PRIVILEGED" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Not for use by third-party applications. --> + <!-- Not for use by third-party applications. --> <permission android:name="android.permission.MASTER_CLEAR" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Allows an application to call any phone number, including emergency + <!-- Allows an application to call any phone number, including emergency numbers, without going through the Dialer user interface for the user to confirm the call being placed. <p>Not for use by third-party applications. --> @@ -3573,19 +3612,19 @@ <permission android:name="android.permission.PERFORM_SIM_ACTIVATION" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Allows enabling/disabling location update notifications from + <!-- Allows enabling/disabling location update notifications from the radio. <p>Not for use by third-party applications. --> <permission android:name="android.permission.CONTROL_LOCATION_UPDATES" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Allows read/write access to the "properties" table in the checkin + <!-- Allows read/write access to the "properties" table in the checkin database, to change values that get uploaded. <p>Not for use by third-party applications. --> <permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Allows an application to collect component usage + <!-- Allows an application to collect component usage statistics <p>Declaring the permission implies intention to use the API and the user of the device can grant permission through the Settings application. --> @@ -3618,7 +3657,7 @@ android:description="@string/permdesc_requestIgnoreBatteryOptimizations" android:protectionLevel="normal" /> - <!-- @SystemApi Allows an application to collect battery statistics --> + <!-- Allows an application to collect battery statistics --> <permission android:name="android.permission.BATTERY_STATS" android:protectionLevel="signature|privileged|development" /> @@ -3647,12 +3686,12 @@ <permission android:name="android.permission.CONFIRM_FULL_BACKUP" android:protectionLevel="signature" /> - <!-- @SystemApi Must be required by a {@link android.widget.RemoteViewsService}, + <!-- Must be required by a {@link android.widget.RemoteViewsService}, to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_REMOTEVIEWS" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi Allows an application to tell the AppWidget service which application + <!-- Allows an application to tell the AppWidget service which application can access AppWidget's data. The normal user flow is that a user picks an AppWidget to go into a particular host, thereby giving that host application access to the private data from the AppWidget app. @@ -3683,7 +3722,7 @@ <permission android:name="android.permission.CHANGE_BACKGROUND_DATA_SETTING" android:protectionLevel="signature" /> - <!-- @SystemApi This permission can be used on content providers to allow the global + <!-- This permission can be used on content providers to allow the global search system to access their data. Typically it used when the provider has some permissions protecting it (which global search would not be expected to hold), and added as a read-only permission @@ -4078,10 +4117,10 @@ confirmation UI for full backup/restore --> <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/> - <!-- Allows the holder to access and manage instant applications on the device. - @hide --> + <!-- @SystemApi Allows the holder to access and manage instant applications on the device. + @hide --> <permission android:name="android.permission.ACCESS_INSTANT_APPS" - android:protectionLevel="signature|installer|verifier" /> + android:protectionLevel="signature|installer|verifier|wellbeing" /> <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS"/> <!-- Allows the holder to view the instant applications on the device. @@ -4177,9 +4216,9 @@ <permission android:name="android.permission.MANAGE_AUTO_FILL" android:protectionLevel="signature" /> - <!-- @SystemApi Allows an application to manage the smart suggestions service. + <!-- @SystemApi Allows an application to manage the content capture service. @hide <p>Not for use by third-party applications.</p> --> - <permission android:name="android.permission.MANAGE_SMART_SUGGESTIONS" + <permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" android:protectionLevel="signature" /> <!-- Allows an app to set the theme overlay in /vendor/overlay diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 56c2e3ed7f0a..f3b4df73dd7f 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -1086,7 +1086,7 @@ <string name="email" msgid="4560673117055050403">"ኢሜይል"</string> <string name="email_desc" msgid="3638665569546416795">"ለተመረጡ አድራሻዎች ኢሜይል ላክ"</string> <string name="dial" msgid="1253998302767701559">"ጥሪ"</string> - <string name="dial_desc" msgid="6573723404985517250">"ወደተመረጠውን ስልክ ቁጥር ደውል"</string> + <string name="dial_desc" msgid="6573723404985517250">"ወደ ተመረጠው ስልክ ቁጥር ደውል"</string> <string name="map" msgid="5441053548030107189">"ካርታ"</string> <string name="map_desc" msgid="1836995341943772348">"የተመረጠውን አድራሻ ያለበትን አግኝ"</string> <string name="browse" msgid="1245903488306147205">"ክፈት"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 2af43ce1dac9..a14a87130ff5 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -537,7 +537,7 @@ <string name="fingerprint_acquired_partial" msgid="735082772341716043">"Fingerabdruck teilweise erkannt. Bitte versuche es noch einmal."</string> <string name="fingerprint_acquired_insufficient" msgid="4596546021310923214">"Fingerabdruck konnte nicht verarbeitet werden. Bitte versuche es noch einmal."</string> <string name="fingerprint_acquired_imager_dirty" msgid="1087209702421076105">"Fingerabdrucksensor ist verschmutzt. Reinige ihn und versuche es noch einmal."</string> - <string name="fingerprint_acquired_too_fast" msgid="6470642383109155969">"Finger zu schnell bewegt. Bitte versuche es noch einmal."</string> + <string name="fingerprint_acquired_too_fast" msgid="6470642383109155969">"Finger zu schnell bewegt, bitte noch einmal versuchen"</string> <string name="fingerprint_acquired_too_slow" msgid="59250885689661653">"Finger zu langsam bewegt. Bitte versuche es noch einmal."</string> <string-array name="fingerprint_acquired_vendor"> </string-array> @@ -549,7 +549,7 @@ <string name="fingerprint_error_timeout" msgid="3927186043737732875">"Zeitüberschreitung für Fingerabdruck. Bitte versuche es noch einmal."</string> <string name="fingerprint_error_canceled" msgid="4402024612660774395">"Fingerabdruckvorgang abgebrochen"</string> <string name="fingerprint_error_user_canceled" msgid="7999639584615291494">"Vorgang der Fingerabdruckauthentifizierung vom Nutzer abgebrochen."</string> - <string name="fingerprint_error_lockout" msgid="5536934748136933450">"Zu viele Versuche. Bitte versuche es später noch einmal."</string> + <string name="fingerprint_error_lockout" msgid="5536934748136933450">"Zu viele Versuche, bitte später noch einmal versuchen"</string> <string name="fingerprint_error_lockout_permanent" msgid="5033251797919508137">"Zu viele Versuche. Der Fingerabdrucksensor wurde deaktiviert."</string> <string name="fingerprint_error_unable_to_process" msgid="6107816084103552441">"Bitte versuche es noch einmal."</string> <string name="fingerprint_error_no_fingerprints" msgid="7654382120628334248">"Keine Fingerabdrücke erfasst."</string> @@ -581,7 +581,7 @@ <string name="face_error_no_space" msgid="8224993703466381314">"Gesicht kann nicht gespeichert werden."</string> <string name="face_error_canceled" msgid="283945501061931023">"Gesichtserkennung abgebrochen."</string> <string name="face_error_user_canceled" msgid="8943921120862164539">"Gesichtsauthentifizierung vom Nutzer abgebrochen."</string> - <string name="face_error_lockout" msgid="3407426963155388504">"Zu viele Versuche. Versuch es später noch einmal."</string> + <string name="face_error_lockout" msgid="3407426963155388504">"Zu viele Versuche, bitte später noch einmal versuchen"</string> <string name="face_error_lockout_permanent" msgid="8198354656746088890">"Zu viele Versuche. Gesichtserkennung deaktiviert."</string> <string name="face_error_unable_to_process" msgid="238761109287767270">"Versuch es noch einmal."</string> <string name="face_error_not_enrolled" msgid="9166792142679691323">"Kein Gesicht erfasst."</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 77a8f054ad54..e633ab46bf25 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -1084,27 +1084,27 @@ <string name="inputMethod" msgid="1653630062304567879">"Sisestusmeetod"</string> <string name="editTextMenuTitle" msgid="4909135564941815494">"Tekstitoimingud"</string> <string name="email" msgid="4560673117055050403">"E-post"</string> - <string name="email_desc" msgid="3638665569546416795">"Valitud aadressile meili saatmine"</string> + <string name="email_desc" msgid="3638665569546416795">"Saada valitud aadressile meil"</string> <string name="dial" msgid="1253998302767701559">"Helista"</string> - <string name="dial_desc" msgid="6573723404985517250">"Valitud telefoninumbrile helistamine"</string> + <string name="dial_desc" msgid="6573723404985517250">"Helista valitud telefoninumbrile"</string> <string name="map" msgid="5441053548030107189">"Kaart"</string> - <string name="map_desc" msgid="1836995341943772348">"Valitud aadressi leidmine"</string> + <string name="map_desc" msgid="1836995341943772348">"Leia valitud aadress"</string> <string name="browse" msgid="1245903488306147205">"Ava"</string> - <string name="browse_desc" msgid="8220976549618935044">"Valitud URL-i avamine"</string> + <string name="browse_desc" msgid="8220976549618935044">"Ava valitud URL"</string> <string name="sms" msgid="4560537514610063430">"Saada sõnum"</string> - <string name="sms_desc" msgid="7526588350969638809">"Valitud telefoninumbrile sõnumi saatmine"</string> + <string name="sms_desc" msgid="7526588350969638809">"Saada valitud telefoninumbrile sõnum"</string> <string name="add_contact" msgid="7867066569670597203">"Lisa"</string> - <string name="add_contact_desc" msgid="4830217847004590345">"Kontaktide hulka lisamine"</string> + <string name="add_contact_desc" msgid="4830217847004590345">"Lisa kontaktide hulka"</string> <string name="view_calendar" msgid="979609872939597838">"Kuva"</string> - <string name="view_calendar_desc" msgid="5828320291870344584">"Valitud aja vaatamine kalendris"</string> + <string name="view_calendar_desc" msgid="5828320291870344584">"Kuva valitud aeg kalendris"</string> <string name="add_calendar_event" msgid="1953664627192056206">"Lisa ajakavasse"</string> - <string name="add_calendar_event_desc" msgid="4326891793260687388">"Ürituse ajastamine valitud ajale"</string> + <string name="add_calendar_event_desc" msgid="4326891793260687388">"Ajasta üritus valitud ajale"</string> <string name="view_flight" msgid="7691640491425680214">"Jälgi"</string> - <string name="view_flight_desc" msgid="3876322502674253506">"Valitud lennu jälgimine"</string> + <string name="view_flight_desc" msgid="3876322502674253506">"Jälgi valitud lendu"</string> <string name="translate" msgid="9218619809342576858">"Tõlgi"</string> - <string name="translate_desc" msgid="4502367770068777202">"Tõlkige valitud tekst"</string> + <string name="translate_desc" msgid="4502367770068777202">"Tõlgi valitud tekst"</string> <string name="define" msgid="7394820043869954211">"Defineeri"</string> - <string name="define_desc" msgid="7910883642444919726">"Valitud teksti defineerimine"</string> + <string name="define_desc" msgid="7910883642444919726">"Defineeri valitud tekst"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Talletusruum saab täis"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Mõned süsteemifunktsioonid ei pruugi töötada"</string> <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Süsteemis pole piisavalt talletusruumi. Veenduge, et seadmes oleks 250 MB vaba ruumi, ja käivitage seade uuesti."</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index bc367be63a54..3f8f0d126032 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1097,7 +1097,7 @@ <string name="add_contact_desc" msgid="4830217847004590345">"Lisää yhteystietoihin"</string> <string name="view_calendar" msgid="979609872939597838">"Näytä"</string> <string name="view_calendar_desc" msgid="5828320291870344584">"Näytä valittu aika kalenterissa"</string> - <string name="add_calendar_event" msgid="1953664627192056206">"Aikataulu"</string> + <string name="add_calendar_event" msgid="1953664627192056206">"Aikatauluta"</string> <string name="add_calendar_event_desc" msgid="4326891793260687388">"Ajoita tapahtuma valitulle ajalle"</string> <string name="view_flight" msgid="7691640491425680214">"Seuraa"</string> <string name="view_flight_desc" msgid="3876322502674253506">"Seuraa valittua lentoa"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index fccb289452a2..87e1f9785591 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -306,7 +306,7 @@ <string name="permgroupdesc_calllog" msgid="3006237336748283775">"कॉल लॉग की जानकारी देखना और उसमें बदलाव करना"</string> <string name="permgrouprequest_calllog" msgid="8487355309583773267">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> को अपने काॅल लाॅग एक्सेस करने की मंज़ूरी देना चाहते हैं?"</string> <string name="permgrouplab_phone" msgid="5229115638567440675">"फ़ोन"</string> - <string name="permgroupdesc_phone" msgid="6234224354060641055">"फ़ोन कॉल करें और प्रबंधित करें"</string> + <string name="permgroupdesc_phone" msgid="6234224354060641055">"फ़ोन कॉल करने और उन्हें प्रबंधित करने की अनुमति दें"</string> <string name="permgrouprequest_phone" msgid="9166979577750581037">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> को फ़ोन कॉल करने और उन्हें प्रबंधित करने की अनुमति दें?"</string> <string name="permgrouplab_sensors" msgid="416037179223226722">"शरीर संवेदक"</string> <string name="permgroupdesc_sensors" msgid="7147968539346634043">"अपने महत्वपूर्ण संकेतों के बारे में सेंसर डेटा को ऐक्सेस करें"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 69b4db23dfbe..cd2af7bb65bf 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -1097,7 +1097,7 @@ <string name="add_contact_desc" msgid="4830217847004590345">"បញ្ចូលទៅក្នុងទំនាក់ទំនង"</string> <string name="view_calendar" msgid="979609872939597838">"មើល"</string> <string name="view_calendar_desc" msgid="5828320291870344584">"មើលពេលវេលាដែលបានជ្រើសរើសនៅក្នុងប្រតិទិន"</string> - <string name="add_calendar_event" msgid="1953664627192056206">"កាលវិភាគ"</string> + <string name="add_calendar_event" msgid="1953664627192056206">"កំណត់កាលវិភាគ"</string> <string name="add_calendar_event_desc" msgid="4326891793260687388">"កំណត់កាលវិភាគព្រឹត្តិការណ៍សម្រាប់ពេលវេលាដែលបានជ្រើសរើស"</string> <string name="view_flight" msgid="7691640491425680214">"តាមដាន"</string> <string name="view_flight_desc" msgid="3876322502674253506">"តាមដានជើងហោះហើរដែលបានជ្រើសរើស"</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index 2232f4d07d4d..f6e9b7215960 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -1083,15 +1083,15 @@ <string name="deleteText" msgid="6979668428458199034">"Жок кылуу"</string> <string name="inputMethod" msgid="1653630062304567879">"Киргизүү ыкмасы"</string> <string name="editTextMenuTitle" msgid="4909135564941815494">"Текст боюнча иштер"</string> - <string name="email" msgid="4560673117055050403">"Электрондук почта"</string> + <string name="email" msgid="4560673117055050403">"Кат жөнөтүү"</string> <string name="email_desc" msgid="3638665569546416795">"Тандалган дарекке электрондук кат жөнөтүү"</string> <string name="dial" msgid="1253998302767701559">"Чалуу"</string> <string name="dial_desc" msgid="6573723404985517250">"Тандалган телефон номерине чалуу"</string> - <string name="map" msgid="5441053548030107189">"Карта"</string> + <string name="map" msgid="5441053548030107189">"Картадан кароо"</string> <string name="map_desc" msgid="1836995341943772348">"Тандалган даректи картада табуу"</string> <string name="browse" msgid="1245903488306147205">"Ачуу"</string> <string name="browse_desc" msgid="8220976549618935044">"Тандалган URL\'ди ачуу"</string> - <string name="sms" msgid="4560537514610063430">"Билдирүү"</string> + <string name="sms" msgid="4560537514610063430">"Билдирүү жазуу"</string> <string name="sms_desc" msgid="7526588350969638809">"Тандалган телефон номерине билдирүү жөнөтүү"</string> <string name="add_contact" msgid="7867066569670597203">"Кошуу"</string> <string name="add_contact_desc" msgid="4830217847004590345">"Байланыштарга кошуу"</string> @@ -1103,7 +1103,7 @@ <string name="view_flight_desc" msgid="3876322502674253506">"Тандалган аба каттамына көз салуу"</string> <string name="translate" msgid="9218619809342576858">"Которуу"</string> <string name="translate_desc" msgid="4502367770068777202">"Тандалган текстти которуу"</string> - <string name="define" msgid="7394820043869954211">"Төмөнкү сөздүн аныктамасын бериңиз:"</string> + <string name="define" msgid="7394820043869954211">"Аныктоо"</string> <string name="define_desc" msgid="7910883642444919726">"Тандалган текстти аныктоо"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Сактагычта орун калбай баратат"</string> <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Системанын кээ бир функциялары иштебеши мүмкүн"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index eec4b31f6eb1..dd4b125681c4 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1107,7 +1107,7 @@ <string name="email_desc" msgid="3638665569546416795">"Nosūtīt e-pasta ziņojumu uz atlasīto adresi"</string> <string name="dial" msgid="1253998302767701559">"Zvanīt"</string> <string name="dial_desc" msgid="6573723404985517250">"Zvanīt uz atlasīto tālruņa numuru"</string> - <string name="map" msgid="5441053548030107189">"Maps"</string> + <string name="map" msgid="5441053548030107189">"Karte"</string> <string name="map_desc" msgid="1836995341943772348">"Atrast atlasīto adresi"</string> <string name="browse" msgid="1245903488306147205">"Atvērt"</string> <string name="browse_desc" msgid="8220976549618935044">"Atvērt atlasīto URL"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index ac2827053b01..93f7e37480cf 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1083,23 +1083,23 @@ <string name="deleteText" msgid="6979668428458199034">"Устгах"</string> <string name="inputMethod" msgid="1653630062304567879">"Оруулах арга"</string> <string name="editTextMenuTitle" msgid="4909135564941815494">"Текст үйлдэл"</string> - <string name="email" msgid="4560673117055050403">"Имэйл"</string> + <string name="email" msgid="4560673117055050403">"Имэйл бичих"</string> <string name="email_desc" msgid="3638665569546416795">"Сонгосон хаяг руу имэйл илгээх"</string> <string name="dial" msgid="1253998302767701559">"Залгах"</string> <string name="dial_desc" msgid="6573723404985517250">"Сонгосон утасны дугаар руу залгах"</string> - <string name="map" msgid="5441053548030107189">"Газрын зураг"</string> + <string name="map" msgid="5441053548030107189">"Газрын зураг хийх"</string> <string name="map_desc" msgid="1836995341943772348">"Сонгосон хаягийг байршуулах"</string> <string name="browse" msgid="1245903488306147205">"Нээх"</string> <string name="browse_desc" msgid="8220976549618935044">"Сонгосон URL-г нээх"</string> - <string name="sms" msgid="4560537514610063430">"Зурвас"</string> + <string name="sms" msgid="4560537514610063430">"Зурвас бичих"</string> <string name="sms_desc" msgid="7526588350969638809">"Сонгосон утасны дугаар руу мессеж илгээх"</string> <string name="add_contact" msgid="7867066569670597203">"Нэмэх"</string> <string name="add_contact_desc" msgid="4830217847004590345">"Харилцагчид нэмэх"</string> <string name="view_calendar" msgid="979609872939597838">"Үзэх"</string> <string name="view_calendar_desc" msgid="5828320291870344584">"Календариас сонгосон огноог харах"</string> - <string name="add_calendar_event" msgid="1953664627192056206">"Хуваарь"</string> + <string name="add_calendar_event" msgid="1953664627192056206">"Хуваарь гаргах"</string> <string name="add_calendar_event_desc" msgid="4326891793260687388">"Aрга хэмжээг сонгосон цагт хуваарилах"</string> - <string name="view_flight" msgid="7691640491425680214">"Бичлэг"</string> + <string name="view_flight" msgid="7691640491425680214">"Хянах"</string> <string name="view_flight_desc" msgid="3876322502674253506">"Сонгосон нислэгийг хянах"</string> <string name="translate" msgid="9218619809342576858">"Орчуулах"</string> <string name="translate_desc" msgid="4502367770068777202">"Сонгосон текстийг орчуулах"</string> diff --git a/core/res/res/values-mr-watch/strings.xml b/core/res/res/values-mr-watch/strings.xml index 223f8faf1b88..f8a52148ed4e 100644 --- a/core/res/res/values-mr-watch/strings.xml +++ b/core/res/res/values-mr-watch/strings.xml @@ -20,6 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="android_upgrading_apk" msgid="1090732262010398759">"<xliff:g id="NUMBER_1">%2$d</xliff:g> पैकी <xliff:g id="NUMBER_0">%1$d</xliff:g> अॅप"</string> + <string name="android_upgrading_apk" msgid="1090732262010398759">"<xliff:g id="NUMBER_1">%2$d</xliff:g> पैकी <xliff:g id="NUMBER_0">%1$d</xliff:g> अॅप"</string> <string name="permgrouplab_sensors" msgid="202675452368612754">"सेन्सर"</string> </resources> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 9a58b386ed6f..902380b6696f 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -183,7 +183,7 @@ <string name="ssl_ca_cert_noti_by_administrator" msgid="3541729986326153557">"आपल्या कार्य प्रोफाइल प्रशासकाद्वारे"</string> <string name="ssl_ca_cert_noti_managed" msgid="4030263497686867141">"<xliff:g id="MANAGING_DOMAIN">%s</xliff:g> द्वारे"</string> <string name="work_profile_deleted" msgid="5005572078641980632">"कार्य प्रोफाईल हटविले"</string> - <string name="work_profile_deleted_details" msgid="6307630639269092360">"कार्य प्रोफाइल प्रशासक अॅप गहाळ आहे किंवा करप्ट आहे. परिणामी, तुमचे कार्य प्रोफाइल आणि संबंधित डेटा हटवले गेले आहेत. सहाय्यासाठी आपल्या प्रशासकाशी संपर्क साधा."</string> + <string name="work_profile_deleted_details" msgid="6307630639269092360">"कार्य प्रोफाइल प्रशासक अॅप गहाळ आहे किंवा करप्ट आहे. परिणामी, तुमचे कार्य प्रोफाइल आणि संबंधित डेटा हटवले गेले आहेत. सहाय्यासाठी आपल्या प्रशासकाशी संपर्क साधा."</string> <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"तुमचे कार्य प्रोफाइल आता या डिव्हाइसवर उपलब्ध नाही"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"बर्याचदा पासवर्ड टाकण्याचा प्रयत्न केला"</string> <string name="network_logging_notification_title" msgid="6399790108123704477">"डिव्हाइस व्यवस्थापित केले आहे"</string> @@ -281,9 +281,9 @@ <string name="permgrouplab_location" msgid="7275582855722310164">"स्थान"</string> <string name="permgroupdesc_location" msgid="1346617465127855033">"या डिव्हाइसच्या स्थानावर प्रवेश"</string> <string name="permgrouprequest_location" msgid="3788275734953323491">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> ला या डिव्हाइसचे स्थान अॅक्सेस करू द्यायचे?"</string> - <string name="permgrouprequestdetail_location" msgid="1113400215566814664">"तुम्ही अॅप वापरत असताना, अॅपला फक्त स्थानाचा अॅक्सेस असेल."</string> + <string name="permgrouprequestdetail_location" msgid="1113400215566814664">"तुम्ही अॅप वापरत असताना, अॅपला फक्त स्थानाचा अॅक्सेस असेल."</string> <string name="permgroupbackgroundrequest_location" msgid="8461841153030844390">"या डिव्हाइसचे स्थान अॅक्सेस करण्याची <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> ला नेहेमी अनुमती द्यायची का?"</string> - <string name="permgroupbackgroundrequestdetail_location" msgid="1715668276378108654">"तुम्ही अॅप वापरत नसलात तरीही, अॅपला स्थानाचा नेहेमी अॅक्सेस असेल."</string> + <string name="permgroupbackgroundrequestdetail_location" msgid="1715668276378108654">"तुम्ही अॅप वापरत नसलात तरीही, अॅपला स्थानाचा नेहेमी अॅक्सेस असेल."</string> <string name="permgrouplab_calendar" msgid="5863508437783683902">"कॅलेंडर"</string> <string name="permgroupdesc_calendar" msgid="3889615280211184106">"आपल्या कॅलेंडरवर प्रवेश"</string> <string name="permgrouprequest_calendar" msgid="289900767793189421">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> ला तुमचे कॅलेंडर अॅक्सेस करू द्यायचे?"</string> @@ -330,11 +330,11 @@ <string name="capability_title_canCaptureFingerprintGestures" msgid="6309568287512278670">"फिंगरप्रिंट जेश्चर"</string> <string name="capability_desc_canCaptureFingerprintGestures" msgid="4386487962402228670">"डिव्हाइसच्या फिंगरप्रिंट सेंसरवर केलेले जेश्चर कॅप्चर करू शकते."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"स्टेटस बार अक्षम करा किंवा सुधारित करा"</string> - <string name="permdesc_statusBar" msgid="8434669549504290975">"स्टेटस बार अक्षम करण्यासाठी किंवा सिस्टम चिन्हे जोडण्यासाठी आणि काढण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_statusBar" msgid="8434669549504290975">"स्टेटस बार अक्षम करण्यासाठी किंवा सिस्टम चिन्हे जोडण्यासाठी आणि काढण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_statusBarService" msgid="4826835508226139688">"स्टेटस बार होऊ द्या"</string> - <string name="permdesc_statusBarService" msgid="716113660795976060">"स्टेटस बार होण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_statusBarService" msgid="716113660795976060">"स्टेटस बार होण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"स्टेटस बार विस्तृत करा/संकुचित करा"</string> - <string name="permdesc_expandStatusBar" msgid="6917549437129401132">"स्टेटस बार विस्तृत करण्यासाठी किंवा संक्षिप्त करण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_expandStatusBar" msgid="6917549437129401132">"स्टेटस बार विस्तृत करण्यासाठी किंवा संक्षिप्त करण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_install_shortcut" msgid="4279070216371564234">"शॉर्टकट स्थापित करा"</string> <string name="permdesc_install_shortcut" msgid="8341295916286736996">"अनुप्रयोगाला वापरकर्ता हस्तक्षेपाशिवाय मुख्यस्क्रीन शॉर्टकट जोडण्याची अनुमती देते."</string> <string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"शॉर्टकट विस्थापित करा"</string> @@ -344,103 +344,103 @@ <string name="permlab_answerPhoneCalls" msgid="4077162841226223337">"फोन कॉलचे उत्तर द्या"</string> <string name="permdesc_answerPhoneCalls" msgid="2901889867993572266">"येणार्या फोन कॉलचे उत्तर देण्यास अॅपला अनुमती देते."</string> <string name="permlab_receiveSms" msgid="8673471768947895082">"मजकूर मेसेज मिळवा (SMS)"</string> - <string name="permdesc_receiveSms" msgid="6424387754228766939">"SMS मेसेज प्राप्त करण्याची आणि त्यावर प्रक्रिया करण्याची अॅप ला अनुमती देते. म्हणजेच अॅप आपल्या डीव्हाइसवर पाठविलेले मेसेज तुम्हाला न दर्शवता त्यांचे परीक्षण करू किंवा ते हटवू शकतो."</string> + <string name="permdesc_receiveSms" msgid="6424387754228766939">"SMS मेसेज प्राप्त करण्याची आणि त्यावर प्रक्रिया करण्याची अॅप ला अनुमती देते. म्हणजेच अॅप आपल्या डीव्हाइसवर पाठविलेले मेसेज तुम्हाला न दर्शवता त्यांचे परीक्षण करू किंवा ते हटवू शकतो."</string> <string name="permlab_receiveMms" msgid="1821317344668257098">"मजकूर मेसेज मिळवा (MMS)"</string> - <string name="permdesc_receiveMms" msgid="533019437263212260">"MMS मेसेज प्राप्त करण्यास आणि त्यावर प्रक्रिया करण्यास अॅप ला अनुमती देते. म्हणजेच अॅप आपल्या डिव्हाइसवर पाठविलेले मेसेज तुम्हाला न दर्शवता त्यांचे परीक्षण करू किंवा ते हटवू शकतो."</string> + <string name="permdesc_receiveMms" msgid="533019437263212260">"MMS मेसेज प्राप्त करण्यास आणि त्यावर प्रक्रिया करण्यास अॅप ला अनुमती देते. म्हणजेच अॅप आपल्या डिव्हाइसवर पाठविलेले मेसेज तुम्हाला न दर्शवता त्यांचे परीक्षण करू किंवा ते हटवू शकतो."</string> <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"सेल प्रसारण मेसेज वाचा"</string> - <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"आपल्या डिव्हाइसद्वारे प्राप्त केलेले सेल प्रसारण मेसेज वाचण्यासाठी अॅप ला अनुमती देते. काही स्थानांमध्ये तुम्हाला आणीबाणीच्या परिस्थितीची चेतावणी देण्यासाठी सेल प्रसारण सूचना वितरीत केल्या जातात. आणीबाणी सेल प्रसारण प्राप्त होते तेव्हा आपल्या डिव्हाइसच्या कार्यप्रदर्शनात किंवा कार्यात दुर्भावनापूर्ण अॅप्स व्यत्यय आणू शकतात."</string> + <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"आपल्या डिव्हाइसद्वारे प्राप्त केलेले सेल प्रसारण मेसेज वाचण्यासाठी अॅप ला अनुमती देते. काही स्थानांमध्ये तुम्हाला आणीबाणीच्या परिस्थितीची चेतावणी देण्यासाठी सेल प्रसारण सूचना वितरीत केल्या जातात. आणीबाणी सेल प्रसारण प्राप्त होते तेव्हा आपल्या डिव्हाइसच्या कार्यप्रदर्शनात किंवा कार्यात दुर्भावनापूर्ण अॅप्स व्यत्यय आणू शकतात."</string> <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"सदस्यता घेतलेली फीड वाचा"</string> - <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"सध्या संकालित केलेल्या फीडविषयी तपशील मिळविण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"सध्या संकालित केलेल्या फीडविषयी तपशील मिळविण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_sendSms" msgid="7544599214260982981">"SMS मेसेज पाठवणे आणि पाहणे"</string> - <string name="permdesc_sendSms" msgid="7094729298204937667">"SMS मेसेज पाठविण्यासाठी अॅप ला अनुमती देते. हे अनपेक्षित शुल्कामुळे होऊ शकते. दुर्भावनापूर्ण अॅप्स नी आपल्या पुष्टिकरणाशिवाय मेसेज पाठवल्यामुळे तुमचे पैसे खर्च होऊ शकतात."</string> + <string name="permdesc_sendSms" msgid="7094729298204937667">"SMS मेसेज पाठविण्यासाठी अॅप ला अनुमती देते. हे अनपेक्षित शुल्कामुळे होऊ शकते. दुर्भावनापूर्ण अॅप्स नी आपल्या पुष्टिकरणाशिवाय मेसेज पाठवल्यामुळे तुमचे पैसे खर्च होऊ शकतात."</string> <string name="permlab_readSms" msgid="8745086572213270480">"तुमचे मजकूर मेसेज वाचा (SMS किंवा MMS)"</string> - <string name="permdesc_readSms" product="tablet" msgid="4741697454888074891">"हा अॅप तुमच्या टॅब्लेटवर स्टोअर केलेले सर्व SMS (मजकूर) मेसेज वाचू शकतो."</string> - <string name="permdesc_readSms" product="tv" msgid="5796670395641116592">"हा अॅप तुमच्या टीव्हीवर स्टोअर केलेले सर्व SMS (मजकूर) मेसेज वाचू शकतो."</string> - <string name="permdesc_readSms" product="default" msgid="6826832415656437652">"हा अॅप तुमच्या फोनवर स्टोअर केलेले सर्व SMS (मजकूर) मेसेज वाचू शकतो."</string> + <string name="permdesc_readSms" product="tablet" msgid="4741697454888074891">"हा अॅप तुमच्या टॅब्लेटवर स्टोअर केलेले सर्व SMS (मजकूर) मेसेज वाचू शकतो."</string> + <string name="permdesc_readSms" product="tv" msgid="5796670395641116592">"हा अॅप तुमच्या टीव्हीवर स्टोअर केलेले सर्व SMS (मजकूर) मेसेज वाचू शकतो."</string> + <string name="permdesc_readSms" product="default" msgid="6826832415656437652">"हा अॅप तुमच्या फोनवर स्टोअर केलेले सर्व SMS (मजकूर) मेसेज वाचू शकतो."</string> <string name="permlab_receiveWapPush" msgid="5991398711936590410">"मजकूर मेसेज मिळवा (WAP)"</string> - <string name="permdesc_receiveWapPush" msgid="748232190220583385">"WAP मेसेज प्राप्त करण्यास आणि त्यावर प्रक्रिया करण्यासाठी अॅप ला अनुमती देते. ही परवानगी तुम्हाला पाठविलेले मेसेज तुम्हाला न दर्शविता त्यांचे परीक्षण करण्याची आणि ते हटविण्याची क्षमता समाविष्ट करते."</string> + <string name="permdesc_receiveWapPush" msgid="748232190220583385">"WAP मेसेज प्राप्त करण्यास आणि त्यावर प्रक्रिया करण्यासाठी अॅप ला अनुमती देते. ही परवानगी तुम्हाला पाठविलेले मेसेज तुम्हाला न दर्शविता त्यांचे परीक्षण करण्याची आणि ते हटविण्याची क्षमता समाविष्ट करते."</string> <string name="permlab_getTasks" msgid="6466095396623933906">"चालणारे अॅप्स पुनर्प्राप्त करा"</string> - <string name="permdesc_getTasks" msgid="7454215995847658102">"सध्या आणि अलीकडे चालणार्या कार्यांविषयी माहिती पुनर्प्राप्त करण्यासाठी अॅप ला अनुमती देते. हे डिव्हाइसवर कोणते अॅप्लिकेशन वापरले जात आहेत त्याविषयी माहिती शोधण्यासाठी अॅप ला अनुमती देऊ शकतात."</string> + <string name="permdesc_getTasks" msgid="7454215995847658102">"सध्या आणि अलीकडे चालणार्या कार्यांविषयी माहिती पुनर्प्राप्त करण्यासाठी अॅप ला अनुमती देते. हे डिव्हाइसवर कोणते अॅप्लिकेशन वापरले जात आहेत त्याविषयी माहिती शोधण्यासाठी अॅप ला अनुमती देऊ शकतात."</string> <string name="permlab_manageProfileAndDeviceOwners" msgid="7918181259098220004">"प्रोफाईल आणि डिव्हाइस मालक व्यवस्थापित करा"</string> <string name="permdesc_manageProfileAndDeviceOwners" msgid="106894851498657169">"प्रोफाईल मालक आणि डिव्हाइस मालक सेट करण्याची अॅप्सना अनुमती द्या."</string> <string name="permlab_reorderTasks" msgid="2018575526934422779">"चालणारे अॅप्स पुनर्क्रमित करा"</string> - <string name="permdesc_reorderTasks" msgid="7734217754877439351">"समोर आणि पार्श्वभूमीवर कार्ये हलविण्यासाठी अॅप ला अनुमती देते. अॅप हे आपल्या इनपुटशिवाय करू शकतो."</string> + <string name="permdesc_reorderTasks" msgid="7734217754877439351">"समोर आणि पार्श्वभूमीवर कार्ये हलविण्यासाठी अॅप ला अनुमती देते. अॅप हे आपल्या इनपुटशिवाय करू शकतो."</string> <string name="permlab_enableCarMode" msgid="5684504058192921098">"कार मोड सुरू करा"</string> - <string name="permdesc_enableCarMode" msgid="4853187425751419467">"कार मोड सक्षम करण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_enableCarMode" msgid="4853187425751419467">"कार मोड सक्षम करण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_killBackgroundProcesses" msgid="3914026687420177202">"अन्य अॅप्स बंद करा"</string> - <string name="permdesc_killBackgroundProcesses" msgid="4593353235959733119">"अन्य अॅप्सच्या पार्श्वभूमी प्रक्रिया समाप्त करण्यासाठी अॅप ला अनुमती देते. यामुळे अन्य अॅप्स चालणे थांबू शकते."</string> - <string name="permlab_systemAlertWindow" msgid="7238805243128138690">"हा अॅप इतर अॅप्सच्या शीर्षस्थानी दिसू शकतो."</string> - <string name="permdesc_systemAlertWindow" msgid="2393776099672266188">"हे अॅप इतर अॅप्सच्या शीर्षस्थानी किंवा स्क्रीनच्या इतर भागांवर दिसू शकतो. हे सामान्य अॅप वापरात व्यत्यय आणू शकते किंवा इतर अॅप्सची डिस्प्ले पद्धत बदलू शकते."</string> + <string name="permdesc_killBackgroundProcesses" msgid="4593353235959733119">"अन्य अॅप्सच्या पार्श्वभूमी प्रक्रिया समाप्त करण्यासाठी अॅप ला अनुमती देते. यामुळे अन्य अॅप्स चालणे थांबू शकते."</string> + <string name="permlab_systemAlertWindow" msgid="7238805243128138690">"हा अॅप इतर अॅप्सच्या शीर्षस्थानी दिसू शकतो."</string> + <string name="permdesc_systemAlertWindow" msgid="2393776099672266188">"हे अॅप इतर अॅप्सच्या शीर्षस्थानी किंवा स्क्रीनच्या इतर भागांवर दिसू शकतो. हे सामान्य अॅप वापरात व्यत्यय आणू शकते किंवा इतर अॅप्सची डिस्प्ले पद्धत बदलू शकते."</string> <string name="permlab_runInBackground" msgid="7365290743781858803">"पार्श्वभूमीत चालवा"</string> - <string name="permdesc_runInBackground" msgid="7370142232209999824">"हे अॅप पार्श्वभूमीत चालू शकते. हे बॅटरी अधिक जलद संपवू शकते."</string> + <string name="permdesc_runInBackground" msgid="7370142232209999824">"हे अॅप पार्श्वभूमीत चालू शकते. हे बॅटरी अधिक जलद संपवू शकते."</string> <string name="permlab_useDataInBackground" msgid="8694951340794341809">"पार्श्वभूमीत डेटा वापरा"</string> <string name="permdesc_useDataInBackground" msgid="6049514223791806027">"हे अॅप पार्श्वभूमीत डेटा वापरू शकते. हे डेटाचा वापर वाढवू शकते."</string> - <string name="permlab_persistentActivity" msgid="8841113627955563938">"अॅप नेहमी चालवा"</string> - <string name="permdesc_persistentActivity" product="tablet" msgid="8525189272329086137">"अॅप ला मेमरीमध्ये कायम असलेले त्याचे स्वतःचे भाग बनविण्यास अनुमती देते. हे टॅबलेट धीमा करून अन्य अॅप्सवर उपलब्ध असलेल्या मेमरीवर मर्यादा घालू शकते."</string> + <string name="permlab_persistentActivity" msgid="8841113627955563938">"अॅप नेहमी चालवा"</string> + <string name="permdesc_persistentActivity" product="tablet" msgid="8525189272329086137">"अॅप ला मेमरीमध्ये कायम असलेले त्याचे स्वतःचे भाग बनविण्यास अनुमती देते. हे टॅबलेट धीमा करून अन्य अॅप्सवर उपलब्ध असलेल्या मेमरीवर मर्यादा घालू शकते."</string> <string name="permdesc_persistentActivity" product="tv" msgid="5086862529499103587">"अॅपला मेमरीमध्ये कायम असलेले त्याचे स्वतःचे भाग बनविण्यासाठी अनुमती देते. हे टीव्ही धीमा करून इतर अॅप्सवर उपलब्ध असलेली मेमरी मर्यादित करू शकते."</string> - <string name="permdesc_persistentActivity" product="default" msgid="4384760047508278272">"अॅप ला मेमरीमध्ये कायम असलेले त्याचे स्वतःचे भाग बनविण्यास अनुमती देते. हे फोन धीमा करून अन्य अॅप्सवर उपलब्ध असलेल्या मेमरीवर मर्यादा घालू शकते."</string> + <string name="permdesc_persistentActivity" product="default" msgid="4384760047508278272">"अॅप ला मेमरीमध्ये कायम असलेले त्याचे स्वतःचे भाग बनविण्यास अनुमती देते. हे फोन धीमा करून अन्य अॅप्सवर उपलब्ध असलेल्या मेमरीवर मर्यादा घालू शकते."</string> <string name="permlab_foregroundService" msgid="3310786367649133115">"पृष्ठभाग सेवा रन करा"</string> <string name="permdesc_foregroundService" msgid="6471634326171344622">"अॅपला पृष्ठभाग सेवा वापरण्याची अनुमती देते."</string> - <string name="permlab_getPackageSize" msgid="7472921768357981986">"अॅप संचयन स्थान मोजा"</string> - <string name="permdesc_getPackageSize" msgid="3921068154420738296">"अॅप ला त्याचा कोड, डेटा आणि कॅश आकार पुनर्प्राप्त करण्यासाठी अनुमती देते"</string> + <string name="permlab_getPackageSize" msgid="7472921768357981986">"अॅप संचयन स्थान मोजा"</string> + <string name="permdesc_getPackageSize" msgid="3921068154420738296">"अॅप ला त्याचा कोड, डेटा आणि कॅश आकार पुनर्प्राप्त करण्यासाठी अनुमती देते"</string> <string name="permlab_writeSettings" msgid="2226195290955224730">"सिस्टम सेटिंग्ज सुधारित करा"</string> - <string name="permdesc_writeSettings" msgid="7775723441558907181">"सिस्टीमचा सेटिंग्ज डेटा सुधारित करण्यासाठी अॅप ला अनुमती देते. दुर्भावनापूर्ण अॅप्स आपल्या सिस्टीमचे कॉन्फिगरेशन दूषित करू शकतात."</string> + <string name="permdesc_writeSettings" msgid="7775723441558907181">"सिस्टीमचा सेटिंग्ज डेटा सुधारित करण्यासाठी अॅप ला अनुमती देते. दुर्भावनापूर्ण अॅप्स आपल्या सिस्टीमचे कॉन्फिगरेशन दूषित करू शकतात."</string> <string name="permlab_receiveBootCompleted" msgid="5312965565987800025">"सुरूवातीस चालवा"</string> - <string name="permdesc_receiveBootCompleted" product="tablet" msgid="7390304664116880704">"जसे सिस्टम बूट करणे समाप्त करते तसे अॅप ला स्वतः प्रारंभ करण्यास अनुमती देते. यामुळे टॅबलेट प्रारंभ करण्यास वेळ लागू शकतो आणि नेहमी चालू राहून एकंदर टॅबलेटला धीमे करण्यास अॅप ला अनुमती देते."</string> + <string name="permdesc_receiveBootCompleted" product="tablet" msgid="7390304664116880704">"जसे सिस्टम बूट करणे समाप्त करते तसे अॅप ला स्वतः प्रारंभ करण्यास अनुमती देते. यामुळे टॅबलेट प्रारंभ करण्यास वेळ लागू शकतो आणि नेहमी चालू राहून एकंदर टॅबलेटला धीमे करण्यास अॅप ला अनुमती देते."</string> <string name="permdesc_receiveBootCompleted" product="tv" msgid="4525890122209673621">"सिस्टम बूट करणे समाप्त करते तसेच अॅपने स्वतः प्रारंभ करण्यास त्याला अनुमती देते. यामुळे टीव्ही प्रारंभ करण्यासाठी त्यास जास्त वेळ लागू शकतो आणि नेहमी चालू ठेवून संपूर्ण टॅबलेट धीमे करण्यासाठी अॅपला अनुमती देते."</string> - <string name="permdesc_receiveBootCompleted" product="default" msgid="513950589102617504">"जसे सिस्टम बूट करणे समाप्त करते तसे अॅप ला स्वतः प्रारंभ करण्यास अनुमती देते. यामुळे फोन प्रारंभ करण्यास वेळ लागू शकतो आणि नेहमी चालू राहून एकंदर फोनला धीमे करण्यास अॅप ला अनुमती देते."</string> + <string name="permdesc_receiveBootCompleted" product="default" msgid="513950589102617504">"जसे सिस्टम बूट करणे समाप्त करते तसे अॅप ला स्वतः प्रारंभ करण्यास अनुमती देते. यामुळे फोन प्रारंभ करण्यास वेळ लागू शकतो आणि नेहमी चालू राहून एकंदर फोनला धीमे करण्यास अॅप ला अनुमती देते."</string> <string name="permlab_broadcastSticky" msgid="7919126372606881614">"रोचक प्रसारण पाठवा"</string> - <string name="permdesc_broadcastSticky" product="tablet" msgid="7749760494399915651">"रोचक प्रसारणे पाठविण्यासाठी अॅप ला अनुमती देते, जे प्रसारण समाप्त झाल्यानंतर देखील तसेच राहते. अत्याधिक वापरामुळे बरीच मेमरी वापरली जाऊन तो टॅब्लेटला धीमा किंवा अस्थिर करू शकतो."</string> + <string name="permdesc_broadcastSticky" product="tablet" msgid="7749760494399915651">"रोचक प्रसारणे पाठविण्यासाठी अॅप ला अनुमती देते, जे प्रसारण समाप्त झाल्यानंतर देखील तसेच राहते. अत्याधिक वापरामुळे बरीच मेमरी वापरली जाऊन तो टॅब्लेटला धीमा किंवा अस्थिर करू शकतो."</string> <string name="permdesc_broadcastSticky" product="tv" msgid="6839285697565389467">"रोचक प्रसारणे पाठविण्यास अॅपला अनुमती देते, जे प्रसारण समाप्त झाल्यानंतर तसेच रहाते. अतिरिक्त वापर टीव्ही धीमा किंवा यासाठी बरीच मेमरी वापरली जात असल्यामुळे तो अस्थिर करू शकतो."</string> - <string name="permdesc_broadcastSticky" product="default" msgid="2825803764232445091">"रोचक प्रसारणे पाठविण्यासाठी अॅप ला अनुमती देते, जे प्रसारण समाप्त झाल्यानंतर देखील तसेच राहते. अत्याधिक वापरामुळे बरीच मेमरी वापरली जाऊन तो फोनला धीमा किंवा अस्थिर करू शकतो."</string> + <string name="permdesc_broadcastSticky" product="default" msgid="2825803764232445091">"रोचक प्रसारणे पाठविण्यासाठी अॅप ला अनुमती देते, जे प्रसारण समाप्त झाल्यानंतर देखील तसेच राहते. अत्याधिक वापरामुळे बरीच मेमरी वापरली जाऊन तो फोनला धीमा किंवा अस्थिर करू शकतो."</string> <string name="permlab_readContacts" msgid="8348481131899886131">"तुमचे संपर्क वाचा"</string> - <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"तुम्ही कॉल केलेल्या, ईमेल केलेल्या किंवा विशिष्ट लोकांशी अन्य मार्गांनी संवाद प्रस्थापित केलेल्या लोकांच्या फ्रिक्वेन्सीसह, आपल्या टॅब्लेटवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा वाचण्यासाठी अॅप ला अनुमती देते. ही परवानगी तुमचा संपर्क डेटा सेव्ह करण्याची अॅप्स ला अनुमती देते आणि दुर्भावनापूर्ण अॅप्स आपल्या माहितीशिवाय संपर्क डेटा शेअर करू शकतात."</string> + <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"तुम्ही कॉल केलेल्या, ईमेल केलेल्या किंवा विशिष्ट लोकांशी अन्य मार्गांनी संवाद प्रस्थापित केलेल्या लोकांच्या फ्रिक्वेन्सीसह, आपल्या टॅब्लेटवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा वाचण्यासाठी अॅप ला अनुमती देते. ही परवानगी तुमचा संपर्क डेटा सेव्ह करण्याची अॅप्स ला अनुमती देते आणि दुर्भावनापूर्ण अॅप्स आपल्या माहितीशिवाय संपर्क डेटा शेअर करू शकतात."</string> <string name="permdesc_readContacts" product="tv" msgid="1839238344654834087">"तुम्ही विशिष्ट लोकांना इतर मार्गांनी कॉल केलेल्या, ईमेल केलेल्या किंवा संप्रेषित केलेल्या फ्रिक्वेन्सीसह, आपल्या टीव्हीवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा वाचण्यासाठी अॅप्सला अनुमती देतात. ही परवागनी अॅप्सला तुमचा संपर्क डेटा सेव्ह करण्यासाठी अनुमती देते आणि दुर्भावनापूर्ण अॅप्स तुम्हाला न कळविता संपर्क डेटा शेअर करू शकतात."</string> - <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"तुम्ही कॉल केलेल्या, ईमेल केलेल्या किंवा विशिष्ट लोकांशी अन्य मार्गांनी संवाद प्रस्थापित केलेल्या लोकांच्या फ्रिक्वेन्सीसह, आपल्या फोनवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा वाचण्यासाठी अॅप ला अनुमती देते. ही परवानगी तुमचा संपर्क डेटा सेव्ह करण्याची अॅप्स ला अनुमती देते आणि दुर्भावनापूर्ण अॅप्स आपल्या माहितीशिवाय संपर्क डेटा शेअर करू शकतात."</string> + <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"तुम्ही कॉल केलेल्या, ईमेल केलेल्या किंवा विशिष्ट लोकांशी अन्य मार्गांनी संवाद प्रस्थापित केलेल्या लोकांच्या फ्रिक्वेन्सीसह, आपल्या फोनवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा वाचण्यासाठी अॅप ला अनुमती देते. ही परवानगी तुमचा संपर्क डेटा सेव्ह करण्याची अॅप्स ला अनुमती देते आणि दुर्भावनापूर्ण अॅप्स आपल्या माहितीशिवाय संपर्क डेटा शेअर करू शकतात."</string> <string name="permlab_writeContacts" msgid="5107492086416793544">"तुमचे संपर्क सुधारित करा"</string> - <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"तुम्ही विशिष्ट संपर्कांशी अन्य मार्गांनी कॉल केलेल्या, ईमेल केलेल्या किंवा संवाद प्रस्थापित केलेल्या फ्रिक्वेन्सीसह, आपल्या टॅब्लेटवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा सुधारित करण्यासाठी अॅप ला अनुमती देते. ही परवानगी संपर्क डेटा हटविण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"तुम्ही विशिष्ट संपर्कांशी अन्य मार्गांनी कॉल केलेल्या, ईमेल केलेल्या किंवा संवाद प्रस्थापित केलेल्या फ्रिक्वेन्सीसह, आपल्या टॅब्लेटवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा सुधारित करण्यासाठी अॅप ला अनुमती देते. ही परवानगी संपर्क डेटा हटविण्यासाठी अॅप ला अनुमती देते."</string> <string name="permdesc_writeContacts" product="tv" msgid="5438230957000018959">"तुम्ही विशिष्ट संपर्कांशी अन्य मार्गांनी कॉल केलेल्या, ईमेल केलेल्या किंवा संवाद प्रस्थापित केलेल्या फ्रिक्वेन्सीसह, आपल्या टीव्हीवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा सुधारित करण्यासाठी अॅपला अनुमती देते. ही परवानगी संपर्क डेटा हटविण्यासाठी अॅपला अनुमती देते."</string> - <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"तुम्ही विशिष्ट संपर्कांशी अन्य मार्गांनी कॉल केलेल्या, ईमेल केलेल्या किंवा संवाद प्रस्थापित केलेल्या फ्रिक्वेन्सीसह, आपल्या फोनवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा सुधारित करण्यासाठी अॅप ला अनुमती देते. ही परवानगी संपर्क डेटा हटविण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"तुम्ही विशिष्ट संपर्कांशी अन्य मार्गांनी कॉल केलेल्या, ईमेल केलेल्या किंवा संवाद प्रस्थापित केलेल्या फ्रिक्वेन्सीसह, आपल्या फोनवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा सुधारित करण्यासाठी अॅप ला अनुमती देते. ही परवानगी संपर्क डेटा हटविण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_readCallLog" msgid="3478133184624102739">"कॉल लॉग वाचा"</string> - <string name="permdesc_readCallLog" msgid="3204122446463552146">"हा अॅप तुमचा कॉल इतिहास वाचू शकता."</string> + <string name="permdesc_readCallLog" msgid="3204122446463552146">"हा अॅप तुमचा कॉल इतिहास वाचू शकता."</string> <string name="permlab_writeCallLog" msgid="8552045664743499354">"कॉल लॉग लिहा"</string> - <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"येणार्या आणि केल्या जाणार्या कॉलविषयीच्या डेटासह, आपल्या टॅब्लेटचा कॉल लॉग सुधारित करण्यासाठी अॅप ला अनुमती देते. दुर्भावनापूर्ण अॅप्स तुमचा कॉल लॉग मिटवण्यासाठी किंवा सुधारित करण्यासाठी याचा वापर करू शकतात."</string> + <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"येणार्या आणि केल्या जाणार्या कॉलविषयीच्या डेटासह, आपल्या टॅब्लेटचा कॉल लॉग सुधारित करण्यासाठी अॅप ला अनुमती देते. दुर्भावनापूर्ण अॅप्स तुमचा कॉल लॉग मिटवण्यासाठी किंवा सुधारित करण्यासाठी याचा वापर करू शकतात."</string> <string name="permdesc_writeCallLog" product="tv" msgid="4225034892248398019">"येणार्या आणि केल्या जाणार्या कॉलविषयीच्या डेटासह, आपल्या टीव्हीचा कॉल लॉग सुधारित करण्यासाठी अॅपला अनुमती देते. दुर्भावनापूर्ण अॅप्स तुमचा कॉल लॉग मिटवण्यासाठी किंवा सुधारित करण्यासाठी याचा वापर करू शकतात."</string> - <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"येणार्या आणि केल्या जाणार्या कॉलविषयीच्या डेटासह, आपल्या फोनचा कॉल लॉग सुधारित करण्यासाठी अॅप ला अनुमती देते. दुर्भावनापूर्ण अॅप्स तुमचा कॉल लॉग मिटवण्यासाठी किंवा सुधारित करण्यासाठी याचा वापर करू शकतात."</string> + <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"येणार्या आणि केल्या जाणार्या कॉलविषयीच्या डेटासह, आपल्या फोनचा कॉल लॉग सुधारित करण्यासाठी अॅप ला अनुमती देते. दुर्भावनापूर्ण अॅप्स तुमचा कॉल लॉग मिटवण्यासाठी किंवा सुधारित करण्यासाठी याचा वापर करू शकतात."</string> <string name="permlab_bodySensors" msgid="4683341291818520277">"शरीर सेंसर (हृदय गती मॉनिटरसारखे) अॅक्सेस करा"</string> <string name="permdesc_bodySensors" product="default" msgid="4380015021754180431">"हृदय गती सारख्या, आपल्या शारीरिक स्थितीचे नियंत्रण करणार्या सेन्सरवरून डेटामध्ये प्रवेश करण्यासाठी अॅपला अनुमती देते."</string> <string name="permlab_readCalendar" msgid="6716116972752441641">"कॅलेंडर इव्हेंट आणि तपशील वाचा"</string> - <string name="permdesc_readCalendar" product="tablet" msgid="4993979255403945892">"हा अॅप आपल्या टॅब्लेटवर स्टोअर केलेले सर्व कॅलेंडर इव्हेंट वाचू आणि शेअर करू शकतो किंवा तुमचा कॅलेंडर डेटा सेव्ह करू शकतो."</string> - <string name="permdesc_readCalendar" product="tv" msgid="8837931557573064315">"हा अॅप आपल्या टीव्हीवर स्टोअर केलेले सर्व कॅलेंडर इव्हेंट वाचू आणि शेअर करू शकतो किंवा तुमचा कॅलेंडर डेटा सेव्ह करू शकतो."</string> - <string name="permdesc_readCalendar" product="default" msgid="4373978642145196715">"हा अॅप आपल्या फोनवर स्टोअर केलेले सर्व कॅलेंडर इव्हेंट वाचू आणि शेअर करू शकतो किंवा तुमचा कॅलेंडर डेटा सेव्ह करू शकतो."</string> + <string name="permdesc_readCalendar" product="tablet" msgid="4993979255403945892">"हा अॅप आपल्या टॅब्लेटवर स्टोअर केलेले सर्व कॅलेंडर इव्हेंट वाचू आणि शेअर करू शकतो किंवा तुमचा कॅलेंडर डेटा सेव्ह करू शकतो."</string> + <string name="permdesc_readCalendar" product="tv" msgid="8837931557573064315">"हा अॅप आपल्या टीव्हीवर स्टोअर केलेले सर्व कॅलेंडर इव्हेंट वाचू आणि शेअर करू शकतो किंवा तुमचा कॅलेंडर डेटा सेव्ह करू शकतो."</string> + <string name="permdesc_readCalendar" product="default" msgid="4373978642145196715">"हा अॅप आपल्या फोनवर स्टोअर केलेले सर्व कॅलेंडर इव्हेंट वाचू आणि शेअर करू शकतो किंवा तुमचा कॅलेंडर डेटा सेव्ह करू शकतो."</string> <string name="permlab_writeCalendar" msgid="8438874755193825647">"कॅलेंडर इव्हेंट जोडा किंवा बदला आणि मालकाला न कळवता अतिथींना ईमेल पाठवा"</string> - <string name="permdesc_writeCalendar" product="tablet" msgid="1675270619903625982">"हा अॅप आपल्या टॅब्लेटवर कॅलेंडर इव्हेंट जोडू, काढू किंवा बदलू शकतो. हा अॅप कॅलेंडर मालकांकडून येत आहेत असे वाटणारे मेसेज पाठवू किंवा त्यांच्या मालकांना सूचित केल्याशिवाय इव्हेंट बदलू शकतो."</string> - <string name="permdesc_writeCalendar" product="tv" msgid="9017809326268135866">"हा अॅप आपल्या टीव्हीवर कॅलेंडर इव्हेंट जोडू, काढू किंवा बदलू शकतो. हा अॅप कॅलेंडर मालकांकडून येत आहेत असे वाटणारे मेसेज पाठवू किंवा त्यांच्या मालकांना सूचित केल्याशिवाय इव्हेंट बदलू शकतो."</string> - <string name="permdesc_writeCalendar" product="default" msgid="7592791790516943173">"हा अॅप आपल्या फोनवर कॅलेंडर इव्हेंट जोडू, काढू किंवा बदलू शकतो. हा अॅप कॅलेंडर मालकांकडून येत आहेत असे वाटणारे मेसेज पाठवू किंवा त्यांच्या मालकांना सूचित केल्याशिवाय इव्हेंट बदलू शकतो."</string> + <string name="permdesc_writeCalendar" product="tablet" msgid="1675270619903625982">"हा अॅप आपल्या टॅब्लेटवर कॅलेंडर इव्हेंट जोडू, काढू किंवा बदलू शकतो. हा अॅप कॅलेंडर मालकांकडून येत आहेत असे वाटणारे मेसेज पाठवू किंवा त्यांच्या मालकांना सूचित केल्याशिवाय इव्हेंट बदलू शकतो."</string> + <string name="permdesc_writeCalendar" product="tv" msgid="9017809326268135866">"हा अॅप आपल्या टीव्हीवर कॅलेंडर इव्हेंट जोडू, काढू किंवा बदलू शकतो. हा अॅप कॅलेंडर मालकांकडून येत आहेत असे वाटणारे मेसेज पाठवू किंवा त्यांच्या मालकांना सूचित केल्याशिवाय इव्हेंट बदलू शकतो."</string> + <string name="permdesc_writeCalendar" product="default" msgid="7592791790516943173">"हा अॅप आपल्या फोनवर कॅलेंडर इव्हेंट जोडू, काढू किंवा बदलू शकतो. हा अॅप कॅलेंडर मालकांकडून येत आहेत असे वाटणारे मेसेज पाठवू किंवा त्यांच्या मालकांना सूचित केल्याशिवाय इव्हेंट बदलू शकतो."</string> <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"अतिरिक्त स्थान प्रदाता आदेश अॅक्सेस करा"</string> <string name="permdesc_accessLocationExtraCommands" msgid="6078307221056649927">"अॅपला अतिरिक्त स्थान प्रदाता आदेशावर प्रवेश करण्याची अनुमती देते. हे कदाचित अॅपला GPS किंवा इतर स्थान स्त्रोत च्या ऑपरेशनमध्ये हस्तक्षेप करण्याची अनुमती देऊ शकते."</string> <string name="permlab_accessFineLocation" msgid="6265109654698562427">"फक्त फोरग्राउंडमध्ये अचूकपणे अॅक्सेस करा"</string> <string name="permdesc_accessFineLocation" msgid="3520508381065331098">"हे अॅप फक्त फोरग्राउंडमध्ये असतानाच तुमचे अचूक स्थान मिळवू शकते. या स्थान सेवा सुरू करणे आणि त्या वापरण्यासाठी अॅपसाठी तुमच्या फोनवर उपलब्ध करणे आवश्यक आहे, यामुळे बॅटरी वापर वाढू शकतो."</string> <string name="permlab_accessCoarseLocation" msgid="3707180371693213469">"फक्त फोरग्राउंडमध्ये अंदाजे स्थान (नेटवर्क आधारित) अॅक्सेस करा"</string> - <string name="permdesc_accessCoarseLocation" product="tablet" msgid="8594719010575779120">"हे अॅप फक्त फोरग्राउंडमध्ये असतानाच, सेल टॉवर आणि वाय-फाय नेटवर्क सारख्या नेटवर्क स्रोतवर आधारित तुमचे स्थान मिळवू शकते. त्या वापरण्याकरता अॅपसाठी, या स्थान सेवा सुरू करणे आणि त्या तुमच्या टॅबलेटवर उपलब्ध करणे आवश्यक आहे."</string> - <string name="permdesc_accessCoarseLocation" product="tv" msgid="3027871910200890806">"हे अॅप फक्त फोरग्राउंडमध्ये असतानाच, सेल टॉवर आणि वाय-फाय नेटवर्क सारख्या नेटवर्क स्रोतवर आधारित तुमचे स्थान मिळवू शकते. त्या वापरण्याकरता अॅपसाठी, या स्थान सेवा सुरू करणे आणि त्या तुमच्या टीव्हीवर उपलब्ध करणे आवश्यक आहे."</string> - <string name="permdesc_accessCoarseLocation" product="default" msgid="854896049371048754">"हे अॅप फक्त फोरग्राउंडमध्ये असतानाच, सेल टॉवर आणि वाय-फाय नेटवर्क सारख्या नेटवर्क स्रोतवर आधारित तुमचे स्थान मिळवू शकते. ते वापरण्याकरता अॅपसाठी, या स्थान सेवा सुरू करणे आणि त्या तुमच्या फोनवर उपलब्ध करणे आवश्यक आहे."</string> + <string name="permdesc_accessCoarseLocation" product="tablet" msgid="8594719010575779120">"हे अॅप फक्त फोरग्राउंडमध्ये असतानाच, सेल टॉवर आणि वाय-फाय नेटवर्क सारख्या नेटवर्क स्रोतवर आधारित तुमचे स्थान मिळवू शकते. त्या वापरण्याकरता अॅपसाठी, या स्थान सेवा सुरू करणे आणि त्या तुमच्या टॅबलेटवर उपलब्ध करणे आवश्यक आहे."</string> + <string name="permdesc_accessCoarseLocation" product="tv" msgid="3027871910200890806">"हे अॅप फक्त फोरग्राउंडमध्ये असतानाच, सेल टॉवर आणि वाय-फाय नेटवर्क सारख्या नेटवर्क स्रोतवर आधारित तुमचे स्थान मिळवू शकते. त्या वापरण्याकरता अॅपसाठी, या स्थान सेवा सुरू करणे आणि त्या तुमच्या टीव्हीवर उपलब्ध करणे आवश्यक आहे."</string> + <string name="permdesc_accessCoarseLocation" product="default" msgid="854896049371048754">"हे अॅप फक्त फोरग्राउंडमध्ये असतानाच, सेल टॉवर आणि वाय-फाय नेटवर्क सारख्या नेटवर्क स्रोतवर आधारित तुमचे स्थान मिळवू शकते. ते वापरण्याकरता अॅपसाठी, या स्थान सेवा सुरू करणे आणि त्या तुमच्या फोनवर उपलब्ध करणे आवश्यक आहे."</string> <string name="permlab_accessBackgroundLocation" msgid="3965397804300661062">"बॅकग्राउंडमध्ये स्थान अॅक्सेस करू शकतो"</string> - <string name="permdesc_accessBackgroundLocation" msgid="1096394429579210251">"याला अंदाजे किंवा अचूक स्थान अॅक्सेस करण्यास अतिरिक्त मंजूरी दिल्यास, बॅकग्राउंडमध्ये चालतांना अॅप स्थान अॅक्सेस करू शकतो."</string> + <string name="permdesc_accessBackgroundLocation" msgid="1096394429579210251">"याला अंदाजे किंवा अचूक स्थान अॅक्सेस करण्यास अतिरिक्त मंजूरी दिल्यास, बॅकग्राउंडमध्ये चालतांना अॅप स्थान अॅक्सेस करू शकतो."</string> <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"आपल्या ऑडिओ सेटिंग्ज बदला"</string> - <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"व्हॉल्यूम आणि आउटपुटसाठी कोणता स्पीकर वापरला आहे यासारख्या समग्र ऑडिओ सेटिंग्ज सुधारित करण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"व्हॉल्यूम आणि आउटपुटसाठी कोणता स्पीकर वापरला आहे यासारख्या समग्र ऑडिओ सेटिंग्ज सुधारित करण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ऑडिओ रेकॉर्ड"</string> - <string name="permdesc_recordAudio" msgid="4245930455135321433">"हा अॅप कोणत्याही वेळी मायक्रोफोन वापरून ऑडिओ रेकॉर्ड करू शकता."</string> + <string name="permdesc_recordAudio" msgid="4245930455135321433">"हा अॅप कोणत्याही वेळी मायक्रोफोन वापरून ऑडिओ रेकॉर्ड करू शकता."</string> <string name="permlab_sim_communication" msgid="2935852302216852065">"सिम वर कमांड पाठवा"</string> <string name="permdesc_sim_communication" msgid="5725159654279639498">"अॅप ला सिम वर कमांड पाठविण्याची अनुमती देते. हे खूप धोकादायक असते."</string> <string name="permlab_activityRecognition" msgid="3634590230567608356">"शारीरिक अॅक्टिव्हिटी ओळखा"</string> - <string name="permdesc_activityRecognition" msgid="3143453925156552894">"हे अॅप तुमच्या शारीरिक अॅक्टिव्हिटी ओळखू शकते."</string> + <string name="permdesc_activityRecognition" msgid="3143453925156552894">"हे अॅप तुमच्या शारीरिक अॅक्टिव्हिटी ओळखू शकते."</string> <string name="permlab_camera" msgid="3616391919559751192">"चित्रे आणि व्हिडिओ घ्या"</string> - <string name="permdesc_camera" msgid="5392231870049240670">"हा अॅप कोणत्याही वेळी कॅमेरा वापरून चित्रेे घेऊ आणि व्हिडिअो रेकॉर्ड करू शकतो."</string> + <string name="permdesc_camera" msgid="5392231870049240670">"हा अॅप कोणत्याही वेळी कॅमेरा वापरून चित्रेे घेऊ आणि व्हिडिअो रेकॉर्ड करू शकतो."</string> <string name="permlab_vibrate" msgid="7696427026057705834">"व्हायब्रेट नियंत्रित करा"</string> - <string name="permdesc_vibrate" msgid="6284989245902300945">"अॅप ला व्हायब्रेटर नियंत्रित करण्यासाठी अनुमती देते."</string> + <string name="permdesc_vibrate" msgid="6284989245902300945">"अॅप ला व्हायब्रेटर नियंत्रित करण्यासाठी अनुमती देते."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"फोन नंबरवर प्रत्यक्ष कॉल करा"</string> - <string name="permdesc_callPhone" msgid="3740797576113760827">"आपल्या हस्तक्षेपाशिवाय फोन नंबरवर कॉल करण्यासाठी अॅप ला अनुमती देते. यामुळे अनपेक्षित शुल्क किंवा कॉल लागू शकतात. लक्षात ठेवा की हे आणीबाणीच्या नंबरवर कॉल करण्यासाठी अॅप ला अनुमती देत नाही. दुर्भावनापूर्ण अॅप्स नी आपल्या पुष्टिकरणाशिवाय कॉल केल्यामुळे तुमचे पैसे खर्च होऊ शकतात."</string> + <string name="permdesc_callPhone" msgid="3740797576113760827">"आपल्या हस्तक्षेपाशिवाय फोन नंबरवर कॉल करण्यासाठी अॅप ला अनुमती देते. यामुळे अनपेक्षित शुल्क किंवा कॉल लागू शकतात. लक्षात ठेवा की हे आणीबाणीच्या नंबरवर कॉल करण्यासाठी अॅप ला अनुमती देत नाही. दुर्भावनापूर्ण अॅप्स नी आपल्या पुष्टिकरणाशिवाय कॉल केल्यामुळे तुमचे पैसे खर्च होऊ शकतात."</string> <string name="permlab_accessImsCallService" msgid="3574943847181793918">"IMS कॉल सेवा अॅक्सेस करा"</string> <string name="permdesc_accessImsCallService" msgid="8992884015198298775">"आपल्या हस्तक्षेपाशिवाय अॅपला कॉल करण्यासाठी IMS सेवा वापरण्याची अनुमती देते."</string> <string name="permlab_readPhoneState" msgid="9178228524507610486">"फोन स्थिती आणि ओळख वाचा"</string> @@ -454,59 +454,59 @@ <string name="permlab_wakeLock" product="tablet" msgid="1531731435011495015">"टॅबलेट निष्क्रिय होण्यापासून प्रतिबंधित करा"</string> <string name="permlab_wakeLock" product="tv" msgid="2601193288949154131">"निष्क्रिय होण्यापासून प्रतिबंध करा"</string> <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"फोन निष्क्रिय होण्यापासून प्रतिबंधित करा"</string> - <string name="permdesc_wakeLock" product="tablet" msgid="7311319824400447868">"टॅब्लेटला निष्क्रिय होण्यापासून प्रतिबंधित करण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_wakeLock" product="tablet" msgid="7311319824400447868">"टॅब्लेटला निष्क्रिय होण्यापासून प्रतिबंधित करण्यासाठी अॅप ला अनुमती देते."</string> <string name="permdesc_wakeLock" product="tv" msgid="3208534859208996974">"निष्क्रिय होण्यापासून टीव्हीला प्रतिबंध करण्यासाठी अॅपला अनुमती देते."</string> - <string name="permdesc_wakeLock" product="default" msgid="8559100677372928754">"फोनला निष्क्रिय होण्यापासून प्रतिबंधित करण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_wakeLock" product="default" msgid="8559100677372928754">"फोनला निष्क्रिय होण्यापासून प्रतिबंधित करण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_transmitIr" msgid="7545858504238530105">"इन्फ्रारेड प्रक्षेपण करा"</string> <string name="permdesc_transmitIr" product="tablet" msgid="5358308854306529170">"अॅप ला टॅब्लेटच्या इन्फ्रारेड ट्रान्समीटरचा वापर करण्याची अनुमती देते."</string> <string name="permdesc_transmitIr" product="tv" msgid="3926790828514867101">"टीव्हीचे इन्फ्रारेड ट्रान्समीटर वापरण्यासाठी अॅपला अनुमती देते."</string> <string name="permdesc_transmitIr" product="default" msgid="7957763745020300725">"अॅप ला फोनच्या इन्फ्रारेड ट्रान्समीटरचा वापर करण्याची अनुमती देते."</string> <string name="permlab_setWallpaper" msgid="6627192333373465143">"वॉलपेपर सेट करा"</string> - <string name="permdesc_setWallpaper" msgid="7373447920977624745">"सिस्टम वॉलपेपर सेट करण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_setWallpaper" msgid="7373447920977624745">"सिस्टम वॉलपेपर सेट करण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_setWallpaperHints" msgid="3278608165977736538">"तुमचा वॉलपेपर आकार समायोजित करा"</string> - <string name="permdesc_setWallpaperHints" msgid="8235784384223730091">"सिस्टम वॉलपेपर आकार सूचना सेट करण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_setWallpaperHints" msgid="8235784384223730091">"सिस्टम वॉलपेपर आकार सूचना सेट करण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_setTimeZone" msgid="2945079801013077340">"टाइम झोन सेट करा"</string> - <string name="permdesc_setTimeZone" product="tablet" msgid="1676983712315827645">"टॅब्लेटचा टाइम झोन बदलण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_setTimeZone" product="tablet" msgid="1676983712315827645">"टॅब्लेटचा टाइम झोन बदलण्यासाठी अॅप ला अनुमती देते."</string> <string name="permdesc_setTimeZone" product="tv" msgid="888864653946175955">"टीव्हीचा टाईम झोन बदलण्यासाठी अॅपला अनुमती देते."</string> - <string name="permdesc_setTimeZone" product="default" msgid="4499943488436633398">"फोनचा टाइम झोन बदलण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_setTimeZone" product="default" msgid="4499943488436633398">"फोनचा टाइम झोन बदलण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_getAccounts" msgid="1086795467760122114">"डिव्हाइसवरील खाती शोधा"</string> - <string name="permdesc_getAccounts" product="tablet" msgid="2741496534769660027">"टॅब्लेटद्वारे ज्ञात खात्यांची सूची मिळवण्यासाठी अॅप ला अनुमती देते. यात तुम्ही इंस्टॉल केलेल्या अनुप्रयोगांद्वारे तयार केलेली कोणतीही खाती समाविष्ट होऊ शकतात."</string> + <string name="permdesc_getAccounts" product="tablet" msgid="2741496534769660027">"टॅब्लेटद्वारे ज्ञात खात्यांची सूची मिळवण्यासाठी अॅप ला अनुमती देते. यात तुम्ही इंस्टॉल केलेल्या अनुप्रयोगांद्वारे तयार केलेली कोणतीही खाती समाविष्ट होऊ शकतात."</string> <string name="permdesc_getAccounts" product="tv" msgid="4190633395633907543">"टीव्हीद्वारे ज्ञात खात्यांची सूची मिळविण्यासाठी अॅपला अनुमती देतो. यात तुम्ही इंस्टॉल केलेल्या अनुप्रयोगांद्वारे तयार केलेली कोणतीही खाती समाविष्ट असू शकतात."</string> - <string name="permdesc_getAccounts" product="default" msgid="3448316822451807382">"फोनद्वारे ज्ञात खात्यांची सूची मिळवण्यासाठी अॅप ला अनुमती देते. यात तुम्ही इंस्टॉल केलेल्या अनुप्रयोगांद्वारे तयार केलेली कोणतीही खाती समाविष्ट करू शकतात."</string> + <string name="permdesc_getAccounts" product="default" msgid="3448316822451807382">"फोनद्वारे ज्ञात खात्यांची सूची मिळवण्यासाठी अॅप ला अनुमती देते. यात तुम्ही इंस्टॉल केलेल्या अनुप्रयोगांद्वारे तयार केलेली कोणतीही खाती समाविष्ट करू शकतात."</string> <string name="permlab_accessNetworkState" msgid="4951027964348974773">"नेटवर्क कनेक्शन पहा"</string> - <string name="permdesc_accessNetworkState" msgid="8318964424675960975">"कोणती नेटवर्क अस्तित्वात आहेत आणि कनेक्ट केलेली आहेत यासारख्या नेटवर्क कनेक्शनविषयीची माहिती पाहण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_accessNetworkState" msgid="8318964424675960975">"कोणती नेटवर्क अस्तित्वात आहेत आणि कनेक्ट केलेली आहेत यासारख्या नेटवर्क कनेक्शनविषयीची माहिती पाहण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_createNetworkSockets" msgid="7934516631384168107">"पूर्ण नेटवर्क प्रवेश आहे"</string> - <string name="permdesc_createNetworkSockets" msgid="3403062187779724185">"नेटवर्क सॉकेट तयार करण्यासाठी आणि कस्टम नेटवर्क प्रोटोकॉल वापरण्यासाठी अॅप ला अनुमती देते. ब्राउझर आणि अन्य अॅप्लिकेशन म्हणजे इंटरनेटवर डेटा पाठवण्याचा मार्ग, म्हणजे इंटरनेटवर डेटा पाठविण्यासाठी परवानगीची आवश्यकता नसते."</string> + <string name="permdesc_createNetworkSockets" msgid="3403062187779724185">"नेटवर्क सॉकेट तयार करण्यासाठी आणि कस्टम नेटवर्क प्रोटोकॉल वापरण्यासाठी अॅप ला अनुमती देते. ब्राउझर आणि अन्य अॅप्लिकेशन म्हणजे इंटरनेटवर डेटा पाठवण्याचा मार्ग, म्हणजे इंटरनेटवर डेटा पाठविण्यासाठी परवानगीची आवश्यकता नसते."</string> <string name="permlab_changeNetworkState" msgid="958884291454327309">"नेटवर्क कनेक्टिव्हिटी बदला"</string> - <string name="permdesc_changeNetworkState" msgid="6789123912476416214">"नेटवर्क कनेक्टिव्हिटीची स्थिती बदलण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_changeNetworkState" msgid="6789123912476416214">"नेटवर्क कनेक्टिव्हिटीची स्थिती बदलण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_changeTetherState" msgid="5952584964373017960">"टिथर केलेली कनेक्टिव्हिटी बदला"</string> - <string name="permdesc_changeTetherState" msgid="1524441344412319780">"टेदर केलेल्या नेटवर्क कनेक्टिव्हिटीची स्थिती बदलण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_changeTetherState" msgid="1524441344412319780">"टेदर केलेल्या नेटवर्क कनेक्टिव्हिटीची स्थिती बदलण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_accessWifiState" msgid="5202012949247040011">"वाय-फाय कनेक्शन पहा"</string> - <string name="permdesc_accessWifiState" msgid="5002798077387803726">"वाय-फाय सक्षम केले आहे किंवा नाही आणि कनेक्ट केलेल्या वाय-फाय डीव्हाइसचे नाव यासारख्या, वाय-फाय नेटवर्किंग विषयीची माहिती पाहण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_accessWifiState" msgid="5002798077387803726">"वाय-फाय सक्षम केले आहे किंवा नाही आणि कनेक्ट केलेल्या वाय-फाय डीव्हाइसचे नाव यासारख्या, वाय-फाय नेटवर्किंग विषयीची माहिती पाहण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_changeWifiState" msgid="6550641188749128035">"वाय-फाय वरून कनेक्ट करा आणि डिस्कनेक्ट करा"</string> <string name="permdesc_changeWifiState" msgid="7137950297386127533">"वाय-फाय अॅक्सेस बिंदूंवर कनेक्ट करण्यासाठी आणि त्यावरून डिस्कनेक्ट करण्यासाठी आणि वाय-फाय नेटवर्कसाठी डिव्हाइस कॉंफिगरेशनमध्ये बदल करण्यासाठी अॅपला अनुमती देते."</string> <string name="permlab_changeWifiMulticastState" msgid="1368253871483254784">"वाय-फाय मल्टिकास्ट रिसेप्शनला अनुमती द्या"</string> - <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="7969774021256336548">"मल्टिकास्ट पत्ते वापरून फक्त तुमच्या टॅब्लेटवर नाही, तर वाय-फाय नेटवर्कवरील सर्व डीव्हाइसवर पाठविलेले पॅकेट प्राप्त करण्यासाठी अॅप ला अनुमती देते. हे मल्टिकास्टखेरिज इतर मोडसाठी अधिक पॉवर वापरते."</string> + <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="7969774021256336548">"मल्टिकास्ट पत्ते वापरून फक्त तुमच्या टॅब्लेटवर नाही, तर वाय-फाय नेटवर्कवरील सर्व डीव्हाइसवर पाठविलेले पॅकेट प्राप्त करण्यासाठी अॅप ला अनुमती देते. हे मल्टिकास्टखेरिज इतर मोडसाठी अधिक पॉवर वापरते."</string> <string name="permdesc_changeWifiMulticastState" product="tv" msgid="9031975661145014160">"केवळ तुमचा टीव्ही न वापरता, एकाधिक पत्ते वापरून एका वाय-फाय नेटवकवरील सर्व डीव्हाइसवर पाठविलेली पॅकेट प्राप्त करण्यासाठी अॅपला अनुमती देते."</string> - <string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"मल्टिकास्ट पत्ते वापरून फक्त तुमच्या फोनवर नाही, तर वाय-फाय नेटवर्कवरील सर्व डीव्हाइसवर पाठविलेले पॅकेट प्राप्त करण्यासाठी अॅप ला अनुमती देते. हे मल्टिकास्टखेरिज इतर मोडसाठी अधिक पॉवर वापरते."</string> + <string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"मल्टिकास्ट पत्ते वापरून फक्त तुमच्या फोनवर नाही, तर वाय-फाय नेटवर्कवरील सर्व डीव्हाइसवर पाठविलेले पॅकेट प्राप्त करण्यासाठी अॅप ला अनुमती देते. हे मल्टिकास्टखेरिज इतर मोडसाठी अधिक पॉवर वापरते."</string> <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"ब्लूटूथ सेटिंग्ज अॅक्सेस करा"</string> - <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"स्थानिक ब्लूटूथ टॅबलेट कॉंफिगर करण्याकरिता आणि दूरस्थ डिव्हाइस शोधण्यासाठी आणि त्यासह जोडण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"स्थानिक ब्लूटूथ टॅबलेट कॉंफिगर करण्याकरिता आणि दूरस्थ डिव्हाइस शोधण्यासाठी आणि त्यासह जोडण्यासाठी अॅप ला अनुमती देते."</string> <string name="permdesc_bluetoothAdmin" product="tv" msgid="3373125682645601429">"स्थानिक ब्लूटूथ टीव्ही कॉंफिगर करण्यासाठी आणि दूरस्थ डीव्हाइससह शोधण्यासाठी आणि जोडण्यासाठी अॅपला अनुमती देते."</string> - <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"स्थानिक ब्लूटूथ फोन कॉंफिगर करण्याकरिता आणि दूरस्थ डिव्हाइस शोधण्यासाठी आणि त्यासह जोडण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"स्थानिक ब्लूटूथ फोन कॉंफिगर करण्याकरिता आणि दूरस्थ डिव्हाइस शोधण्यासाठी आणि त्यासह जोडण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_accessWimaxState" msgid="4195907010610205703">"WiMAX कनेक्ट करा आणि त्यावरून डिस्कनेक्ट करा"</string> - <string name="permdesc_accessWimaxState" msgid="6360102877261978887">"WiMAX सक्षम केले आहे किंवा नाही आणि कनेक्ट केलेल्या कोणत्याही WiMAX नेटवर्क विषयीची माहिती निर्धारित करण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_accessWimaxState" msgid="6360102877261978887">"WiMAX सक्षम केले आहे किंवा नाही आणि कनेक्ट केलेल्या कोणत्याही WiMAX नेटवर्क विषयीची माहिती निर्धारित करण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_changeWimaxState" msgid="340465839241528618">"WiMAX स्थिती बदला"</string> - <string name="permdesc_changeWimaxState" product="tablet" msgid="3156456504084201805">"WiMAX नेटवर्कवर टॅबलेट कनेक्ट करण्यास आणि त्यावरून टॅबलेट डिस्कनेक्ट करण्यास अॅप ला अनुमती देते."</string> + <string name="permdesc_changeWimaxState" product="tablet" msgid="3156456504084201805">"WiMAX नेटवर्कवर टॅबलेट कनेक्ट करण्यास आणि त्यावरून टॅबलेट डिस्कनेक्ट करण्यास अॅप ला अनुमती देते."</string> <string name="permdesc_changeWimaxState" product="tv" msgid="6022307083934827718">"WiMAX नेटवर्कवरून टीव्ही कनेक्ट करण्यासाठी आणि त्यावरून टीव्ही डिस्कनेक्ट करण्यासाठी अॅपला अनुमती देते."</string> - <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"WiMAX नेटवर्कवर फोन कनेक्ट करण्यास आणि त्यावरून फोन डिस्कनेक्ट करण्यास अॅप ला अनुमती देते."</string> + <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"WiMAX नेटवर्कवर फोन कनेक्ट करण्यास आणि त्यावरून फोन डिस्कनेक्ट करण्यास अॅप ला अनुमती देते."</string> <string name="permlab_bluetooth" msgid="6127769336339276828">"ब्लूटूथ डीव्हाइससह जोडा"</string> - <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"टॅबलेटवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string> + <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"टॅबलेटवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string> <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"टीव्हीवर ब्लूटूथचे कॉंफिगरेशन पाहण्यासाठी आणि जोडलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी अॅपला अनुमती देते."</string> - <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"फोनवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string> + <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"फोनवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string> <string name="permlab_nfc" msgid="4423351274757876953">"फील्ड जवळील कम्युनिकेशन नियंत्रित करा"</string> <string name="permdesc_nfc" msgid="7120611819401789907">"फील्ड जवळील कम्युनिकेशन (NFC) टॅग, कार्डे आणि वाचक यांच्यासह संवाद करण्यासाठी अॅपला अनुमती देते."</string> <string name="permlab_disableKeyguard" msgid="3598496301486439258">"तुमचे स्क्रीन लॉक अक्षम करा"</string> - <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"कीलॉक आणि कोणतीही संबद्ध पासवर्ड सुरक्षितता अक्षम करण्यासाठी अॅप ला अनुमती देते. उदाहरणार्थ, येणारा फोन कॉल प्राप्त करताना फोन कीलॉक अक्षम करतो, नंतर जेव्हा कॉल समाप्त होतो तेव्हा तो कीलॉक पुन्हा-सक्षम करतो."</string> + <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"कीलॉक आणि कोणतीही संबद्ध पासवर्ड सुरक्षितता अक्षम करण्यासाठी अॅप ला अनुमती देते. उदाहरणार्थ, येणारा फोन कॉल प्राप्त करताना फोन कीलॉक अक्षम करतो, नंतर जेव्हा कॉल समाप्त होतो तेव्हा तो कीलॉक पुन्हा-सक्षम करतो."</string> <string name="permlab_useBiometric" msgid="8837753668509919318">"बायोमेट्रिक हार्डवेअर वापरा"</string> <string name="permdesc_useBiometric" msgid="8389855232721612926">"ऑथेंटिकेशनसाठी बायोमेट्रिक हार्डवेअरचा वापर करण्याची अॅपला अनुमती देते"</string> <string name="permlab_manageFingerprint" msgid="5640858826254575638">"फिंगरप्रिंट हार्डवेअर व्यवस्थापित करा"</string> @@ -591,11 +591,11 @@ </string-array> <string name="face_icon_content_description" msgid="4024817159806482191">"चेहरा आयकन"</string> <string name="permlab_readSyncSettings" msgid="6201810008230503052">"सिंक सेटिंग्ज वाचा"</string> - <string name="permdesc_readSyncSettings" msgid="2706745674569678644">"खात्याच्या सिंक सेटिंग्ज वाचण्यासाठी अॅप ला अनुमती देते. उदाहरणार्थ, हे खात्यासह लोकांचा अॅप संकालित केला आहे किंवा नाही हे निर्धारित करू शकते."</string> + <string name="permdesc_readSyncSettings" msgid="2706745674569678644">"खात्याच्या सिंक सेटिंग्ज वाचण्यासाठी अॅप ला अनुमती देते. उदाहरणार्थ, हे खात्यासह लोकांचा अॅप संकालित केला आहे किंवा नाही हे निर्धारित करू शकते."</string> <string name="permlab_writeSyncSettings" msgid="5408694875793945314">"सिंक चालू आणि बंद करा टॉगल करा"</string> - <string name="permdesc_writeSyncSettings" msgid="8956262591306369868">"खात्यासाठी सिंक सेटिंग्ज सुधारित करण्यासाठी अॅप ला अनुमती देते. उदाहरणार्थ, हे खात्यासह लोकांच्या अॅप चे सिंक सक्षम करण्यासाठी वापरले जाऊ शकते."</string> + <string name="permdesc_writeSyncSettings" msgid="8956262591306369868">"खात्यासाठी सिंक सेटिंग्ज सुधारित करण्यासाठी अॅप ला अनुमती देते. उदाहरणार्थ, हे खात्यासह लोकांच्या अॅप चे सिंक सक्षम करण्यासाठी वापरले जाऊ शकते."</string> <string name="permlab_readSyncStats" msgid="7396577451360202448">"सिंक आकडेवारी वाचा"</string> - <string name="permdesc_readSyncStats" msgid="1510143761757606156">"सिंक इव्हेंटचा इतिहास आणि किती डेटाचे सिंक केले आहे यासह, खात्याची सिंक स्थिती वाचण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_readSyncStats" msgid="1510143761757606156">"सिंक इव्हेंटचा इतिहास आणि किती डेटाचे सिंक केले आहे यासह, खात्याची सिंक स्थिती वाचण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_sdcardRead" msgid="1438933556581438863">"तुमच्या शेअर केलेल्या स्टोरेजचे आशय वाचते"</string> <string name="permdesc_sdcardRead" msgid="1804941689051236391">"अॅपला तुमच्या शेअर केलेल्या स्टोरेजचे आशय वाचण्याची अनुमती देते."</string> <string name="permlab_sdcardWrite" msgid="9220937740184960897">"तुमच्या शेअर केलेल्या स्टोरेजच्या आशयांमध्ये सुधारणा करा किंवा हटवा"</string> @@ -607,17 +607,17 @@ <string name="permlab_register_call_provider" msgid="108102120289029841">"नवीन टेलिकॉम कनेक्शनची नोंदणी करा"</string> <string name="permdesc_register_call_provider" msgid="7034310263521081388">"नवीन टेलिकॉम कनेक्शनची नोंदणी करण्यासाठी अॅपला अनुमती देते."</string> <string name="permlab_connection_manager" msgid="1116193254522105375">"टेलिकॉम कनेक्शन व्यवस्थापित करा"</string> - <string name="permdesc_connection_manager" msgid="5925480810356483565">"टेलिकॉम कनेक्शन व्यवस्थापित करण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_connection_manager" msgid="5925480810356483565">"टेलिकॉम कनेक्शन व्यवस्थापित करण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_bind_incall_service" msgid="6773648341975287125">"कॉल-मधील स्क्रीनशी परस्परसंवाद करा"</string> <string name="permdesc_bind_incall_service" msgid="8343471381323215005">"वापरकर्ता कॉल-मधील स्क्रीन केव्हा आणि कशी पाहतो ते नियंत्रित करण्याची अॅपला अनुमती देते."</string> <string name="permlab_bind_connection_service" msgid="3557341439297014940">"टेलिफोनी सेवांशी परस्परसंवाद साधा"</string> - <string name="permdesc_bind_connection_service" msgid="4008754499822478114">"कॉल करण्यासाठी/घेण्यासाठी टेलिफोनी सेवांशी परस्परसंवाद साधण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_bind_connection_service" msgid="4008754499822478114">"कॉल करण्यासाठी/घेण्यासाठी टेलिफोनी सेवांशी परस्परसंवाद साधण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_control_incall_experience" msgid="9061024437607777619">"एक कॉल-मधील वापरकर्ता अनुभव प्रदान करा"</string> <string name="permdesc_control_incall_experience" msgid="915159066039828124">"अॅप्सला कॉल-मधील वापरकर्ता अनुभव प्रदान करण्याची अनुमती देते."</string> <string name="permlab_readNetworkUsageHistory" msgid="7862593283611493232">"ऐतिहासिक नेटवर्क वापर वाचा"</string> <string name="permdesc_readNetworkUsageHistory" msgid="7689060749819126472">"विशिष्ट नेटवर्क आणि अॅप्सकरिता ऐतिहासिक नेटवर्क वापराचे वाचन करण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_manageNetworkPolicy" msgid="2562053592339859990">"नेटवर्क धोरण व्यवस्थापित करा"</string> - <string name="permdesc_manageNetworkPolicy" msgid="7537586771559370668">"नेटवर्क धोरणे व्यवस्थापित करण्यासाठी आणि अॅप-विशिष्ट नियम परिभाषित करण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_manageNetworkPolicy" msgid="7537586771559370668">"नेटवर्क धोरणे व्यवस्थापित करण्यासाठी आणि अॅप-विशिष्ट नियम परिभाषित करण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_modifyNetworkAccounting" msgid="5088217309088729650">"नेटवर्क वापर हिशोब सुधारित करा"</string> <string name="permdesc_modifyNetworkAccounting" msgid="5443412866746198123">"अॅप्स वर नेटवर्क वापराचा हिशोब कसा घेतला जातो हे सुधारित करण्यासाठी अॅप्स ला अनुमती देते. सामान्य अॅप्सद्वारे वापरण्यासाठी नाही."</string> <string name="permlab_accessNotifications" msgid="7673416487873432268">"प्रवेश सूचना"</string> @@ -633,7 +633,7 @@ <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"नेटवर्क स्थितींवरील निरीक्षणांसाठी ऐका"</string> <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"अनु्प्रयोगाला नेटवर्क स्थितींवरील निरीक्षणे ऐकण्यासाठी अनुमती देते. सामान्य अॅप्ससाठी कधीही आवश्यक नसावे."</string> <string name="permlab_setInputCalibration" msgid="4902620118878467615">"इनपुट डिव्हाइस कॅलिब्रेशन बदला"</string> - <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"स्पर्श स्क्रीनची कॅलिब्रेशन प्राचले सुधारित करण्यासाठी अॅप ला अनुमती देते. सामान्य अॅप्स साठी कधीही आवश्यक नसते."</string> + <string name="permdesc_setInputCalibration" msgid="4527511047549456929">"स्पर्श स्क्रीनची कॅलिब्रेशन प्राचले सुधारित करण्यासाठी अॅप ला अनुमती देते. सामान्य अॅप्स साठी कधीही आवश्यक नसते."</string> <string name="permlab_accessDrmCertificates" msgid="7436886640723203615">"DRM प्रमाणपत्रे अॅक्सेस करा"</string> <string name="permdesc_accessDrmCertificates" msgid="8073288354426159089">"DRM प्रमाणपत्रांची तरतूद करण्यासाठी आणि वापरण्यासाठी अनुप्रयोगास अनुमती देते. सामान्य अॅप्सकरिता कधीही आवश्यकता नसते."</string> <string name="permlab_handoverStatus" msgid="7820353257219300883">"Android बीम स्थानांतरण स्थिती प्राप्त करा"</string> @@ -672,7 +672,7 @@ <string name="policylab_expirePassword" msgid="5610055012328825874">"स्क्रीन लॉक पासवर्ड कालबाह्यता सेट करा"</string> <string name="policydesc_expirePassword" msgid="5367525762204416046">"लॉक-स्क्रीन पासवर्ड किती वारंवार बदलणे आवश्यक आहे ते बदला."</string> <string name="policylab_encryptedStorage" msgid="8901326199909132915">"स्टोरेज एंक्रिप्शन सेट करा"</string> - <string name="policydesc_encryptedStorage" msgid="2637732115325316992">"स्टोअर केलेला अॅप डेटा एंक्रिप्ट केला जाणे आवश्यक आहे."</string> + <string name="policydesc_encryptedStorage" msgid="2637732115325316992">"स्टोअर केलेला अॅप डेटा एंक्रिप्ट केला जाणे आवश्यक आहे."</string> <string name="policylab_disableCamera" msgid="6395301023152297826">"कॅमेरे अक्षम करा"</string> <string name="policydesc_disableCamera" msgid="2306349042834754597">"सर्व डिव्हाइस कॅमेर्यांचा वापर प्रतिबंधित करा."</string> <string name="policylab_disableKeyguardFeatures" msgid="8552277871075367771">"काही स्क्रीन लॉक वैशिष्ट्ये अक्षम करा"</string> @@ -897,7 +897,7 @@ <string name="granularity_label_link" msgid="5815508880782488267">"लिंक"</string> <string name="granularity_label_line" msgid="5764267235026120888">"रेखा"</string> <string name="factorytest_failed" msgid="5410270329114212041">"फॅक्टरी चाचणी अयशस्वी"</string> - <string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST क्रिया फक्त /सिस्टम/अॅप मध्ये इंस्टॉल केलेल्या पॅकेजसाठी समर्थित आहे."</string> + <string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST क्रिया फक्त /सिस्टम/अॅप मध्ये इंस्टॉल केलेल्या पॅकेजसाठी समर्थित आहे."</string> <string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST क्रिया प्रदान करणारे कोणतेही पॅकेज आढळले नाही."</string> <string name="factorytest_reboot" msgid="6320168203050791643">"रीबूट करा"</string> <string name="js_dialog_title" msgid="1987483977834603872">"\"<xliff:g id="TITLE">%s</xliff:g>\" वरील पृष्ठ हे म्हणते:"</string> @@ -928,17 +928,17 @@ <string name="autofill_area" msgid="3547409050889952423">"क्षेत्र"</string> <string name="autofill_emirate" msgid="2893880978835698818">"अमिरात"</string> <string name="permlab_readHistoryBookmarks" msgid="3775265775405106983">"तुमचे वेब बुकमार्क आणि इतिहास वाचा"</string> - <string name="permdesc_readHistoryBookmarks" msgid="8462378226600439658">"ब्राउझरने भेट दिलेल्या सर्व URL चा इतिहास आणि ब्राउझरचे सर्व बुकमार्क वाचण्यास अॅप ला अनुमती देते. टीप: या परवानगीची तृतीय-पक्ष ब्राउझरद्वारे किंवा वेब ब्राउझिंग क्षमता असलेल्या अन्य अनुप्रयोगांद्वारे अंमलबजावणी करू शकत नाही."</string> + <string name="permdesc_readHistoryBookmarks" msgid="8462378226600439658">"ब्राउझरने भेट दिलेल्या सर्व URL चा इतिहास आणि ब्राउझरचे सर्व बुकमार्क वाचण्यास अॅप ला अनुमती देते. टीप: या परवानगीची तृतीय-पक्ष ब्राउझरद्वारे किंवा वेब ब्राउझिंग क्षमता असलेल्या अन्य अनुप्रयोगांद्वारे अंमलबजावणी करू शकत नाही."</string> <string name="permlab_writeHistoryBookmarks" msgid="3714785165273314490">"वेब बुकमार्क आणि इतिहास लिहा"</string> - <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="6825527469145760922">"तुमच्या टॅब्लेटवर स्टोअर केलेला ब्राउझरचा इतिहास किंवा बुकमार्क सुधारित करण्यासाठी अॅप ला अनुमती देते. हे ब्राउझर डेटा मिटविण्यासाठी किंवा सुधारित करण्यासाठी अॅप ला अनुमती देते. टीप: ही परवानगी तृतीय पक्ष ब्राउझरद्वारे किंवा वेब ब्राउझिंग क्षमतांसह अन्य अॅप्लिकेशनद्वारे अंमलबजावणी करण्याची टीप देऊ शकते."</string> + <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="6825527469145760922">"तुमच्या टॅब्लेटवर स्टोअर केलेला ब्राउझरचा इतिहास किंवा बुकमार्क सुधारित करण्यासाठी अॅप ला अनुमती देते. हे ब्राउझर डेटा मिटविण्यासाठी किंवा सुधारित करण्यासाठी अॅप ला अनुमती देते. टीप: ही परवानगी तृतीय पक्ष ब्राउझरद्वारे किंवा वेब ब्राउझिंग क्षमतांसह अन्य अॅप्लिकेशनद्वारे अंमलबजावणी करण्याची टीप देऊ शकते."</string> <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="7007393823197766548">"तुमच्या टीव्हीवर स्टोअर केलेला ब्राउझरचा इतिहास किंवा बुकमार्क सुधारित करण्यासाठी अॅपला अनुमती देते. हे ब्राउझर डेटा मिटविण्यासाठी किंवा सुधारित करण्यासाठी अॅपला अनुमती देऊ शकते. टीप: या परवानगीची अंमलबजावणी वेब ब्राउझिंग क्षमता असलेल्या तृतीय-पक्ष ब्राउझरद्वारे किंवा इतर अॅप्लिकेशनद्वारे केली जाऊ शकत नाही."</string> - <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"तुमच्या फोनवर स्टोअर केलेला ब्राउझरचा इतिहास किंवा बुकमार्क सुधारित करण्यासाठी अॅप ला अनुमती देते. हे ब्राउझर डेटा मिटविण्यासाठी किंवा सुधारित करण्यासाठी अॅप ला अनुमती देते. टीप: ही परवानगी तृतीय पक्ष ब्राउझरद्वारे किंवा वेब ब्राउझिंग क्षमतांसह अन्य अॅप्लिकेशनद्वारे अंमलबजावणी करण्याची टीप देऊ शकते."</string> + <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"तुमच्या फोनवर स्टोअर केलेला ब्राउझरचा इतिहास किंवा बुकमार्क सुधारित करण्यासाठी अॅप ला अनुमती देते. हे ब्राउझर डेटा मिटविण्यासाठी किंवा सुधारित करण्यासाठी अॅप ला अनुमती देते. टीप: ही परवानगी तृतीय पक्ष ब्राउझरद्वारे किंवा वेब ब्राउझिंग क्षमतांसह अन्य अॅप्लिकेशनद्वारे अंमलबजावणी करण्याची टीप देऊ शकते."</string> <string name="permlab_setAlarm" msgid="1379294556362091814">"अलार्म सेट करा"</string> <string name="permdesc_setAlarm" msgid="316392039157473848">"इंस्टॉल केलेल्या अलार्म घड्याळ अॅपमध्ये अलार्म सेट करण्यासाठी अॅपला अनुमती देते. काही अलार्म घड्याळ अॅप्समध्ये हे वैशिष्ट्य नसू शकते."</string> <string name="permlab_addVoicemail" msgid="5525660026090959044">"व्हॉइसमेल जोडा"</string> - <string name="permdesc_addVoicemail" msgid="6604508651428252437">"आपल्या व्हॉइसमेल इनबॉक्समध्ये मेसेज जोडण्यासाठी अॅप ला अनुमती देते."</string> + <string name="permdesc_addVoicemail" msgid="6604508651428252437">"आपल्या व्हॉइसमेल इनबॉक्समध्ये मेसेज जोडण्यासाठी अॅप ला अनुमती देते."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ब्राउझर भौगोलिक स्थान परवानग्या सुधारित करा"</string> - <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ब्राउझरच्या भौगोलिक स्थान परवानग्या सुधारित करण्यासाठी अॅप ला अनुमती देते. दुर्भावनापूर्ण अॅप्स यादृच्छिक वेबसाइटवर स्थान माहिती पाठविण्यास अनुमती देण्यासाठी याचा वापर करू शकतात."</string> + <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ब्राउझरच्या भौगोलिक स्थान परवानग्या सुधारित करण्यासाठी अॅप ला अनुमती देते. दुर्भावनापूर्ण अॅप्स यादृच्छिक वेबसाइटवर स्थान माहिती पाठविण्यास अनुमती देण्यासाठी याचा वापर करू शकतात."</string> <string name="save_password_message" msgid="767344687139195790">"ब्राउझरने हा पासवर्ड लक्षात ठेवावा असे तुम्ही इच्छिता?"</string> <string name="save_password_notnow" msgid="6389675316706699758">"आत्ता नाही"</string> <string name="save_password_remember" msgid="6491879678996749466">"लक्षात ठेवा"</string> @@ -1083,25 +1083,25 @@ <string name="deleteText" msgid="6979668428458199034">"हटवा"</string> <string name="inputMethod" msgid="1653630062304567879">"इनपुट पद्धत"</string> <string name="editTextMenuTitle" msgid="4909135564941815494">"मजकूर क्रिया"</string> - <string name="email" msgid="4560673117055050403">"ईमेल"</string> + <string name="email" msgid="4560673117055050403">"ईमेल करा"</string> <string name="email_desc" msgid="3638665569546416795">"निवडलेल्या अॅड्रेसवर ईमेल करा"</string> <string name="dial" msgid="1253998302767701559">"कॉल करा"</string> <string name="dial_desc" msgid="6573723404985517250">"निवडलेल्या फोन नंबरवर कॉल करा"</string> - <string name="map" msgid="5441053548030107189">"नकाशा"</string> + <string name="map" msgid="5441053548030107189">"नकाशा उघडा"</string> <string name="map_desc" msgid="1836995341943772348">"निवडलेला पत्ता शोधा"</string> <string name="browse" msgid="1245903488306147205">"उघडा"</string> <string name="browse_desc" msgid="8220976549618935044">"निवडलेली URL उघडा"</string> - <string name="sms" msgid="4560537514610063430">"मेसेज"</string> + <string name="sms" msgid="4560537514610063430">"मेसेज करा"</string> <string name="sms_desc" msgid="7526588350969638809">"निवडलेल्या फोन नंबरवर एसएमएस करा"</string> <string name="add_contact" msgid="7867066569670597203">"जोडा"</string> <string name="add_contact_desc" msgid="4830217847004590345">"संपर्कांमध्ये जोडा"</string> - <string name="view_calendar" msgid="979609872939597838">"पहा"</string> + <string name="view_calendar" msgid="979609872939597838">"पाहा"</string> <string name="view_calendar_desc" msgid="5828320291870344584">"निवडलेली वेळ कॅलेंडरमध्ये पाहा"</string> - <string name="add_calendar_event" msgid="1953664627192056206">"शेड्यूल"</string> - <string name="add_calendar_event_desc" msgid="4326891793260687388">"निवडलेल्या वेळेसाठी इव्हेंट शेड्यूल करा"</string> - <string name="view_flight" msgid="7691640491425680214">"ट्रॅक"</string> + <string name="add_calendar_event" msgid="1953664627192056206">"शेड्युल करा"</string> + <string name="add_calendar_event_desc" msgid="4326891793260687388">"निवडलेल्या वेळेसाठी इव्हेंट शेड्युल करा"</string> + <string name="view_flight" msgid="7691640491425680214">"ट्रॅक करा"</string> <string name="view_flight_desc" msgid="3876322502674253506">"निवडलेले विमान ट्रॅक करा"</string> - <string name="translate" msgid="9218619809342576858">"भाषांतर"</string> + <string name="translate" msgid="9218619809342576858">"भाषांतर करा"</string> <string name="translate_desc" msgid="4502367770068777202">"निवडलेल्या मजकुराचे भाषांतर करा"</string> <string name="define" msgid="7394820043869954211">"व्याख्या सांगा"</string> <string name="define_desc" msgid="7910883642444919726">"निवडलेल्या मजकुराची व्याख्या सांगा"</string> @@ -1143,18 +1143,18 @@ <string name="use_a_different_app" msgid="8134926230585710243">"एक भिन्न अॅप वापरा"</string> <string name="clearDefaultHintMsg" msgid="3252584689512077257">"डाउनलोड केलेल्या सिस्टम सेटिंग्ज > Apps > मधील डीफॉल्ट साफ करा."</string> <string name="chooseActivity" msgid="7486876147751803333">"क्रिया निवडा"</string> - <string name="chooseUsbActivity" msgid="6894748416073583509">"USB डिव्हाइससाठी अॅप निवडा"</string> + <string name="chooseUsbActivity" msgid="6894748416073583509">"USB डिव्हाइससाठी अॅप निवडा"</string> <string name="noApplications" msgid="2991814273936504689">"कोणतेही अॅप्स ही क्रिया करू शकत नाहीत."</string> <string name="aerr_application" msgid="250320989337856518">"<xliff:g id="APPLICATION">%1$s</xliff:g> थांबला आहे"</string> <string name="aerr_process" msgid="6201597323218674729">"<xliff:g id="PROCESS">%1$s</xliff:g> थांबली आहे"</string> <string name="aerr_application_repeated" msgid="3146328699537439573">"<xliff:g id="APPLICATION">%1$s</xliff:g> थांबतो"</string> <string name="aerr_process_repeated" msgid="6235302956890402259">"<xliff:g id="PROCESS">%1$s</xliff:g> थांबते"</string> - <string name="aerr_restart" msgid="7581308074153624475">"अॅप पुन्हा उघडा"</string> + <string name="aerr_restart" msgid="7581308074153624475">"अॅप पुन्हा उघडा"</string> <string name="aerr_report" msgid="5371800241488400617">"अभिप्राय पाठवा"</string> <string name="aerr_close" msgid="2991640326563991340">"बंद करा"</string> <string name="aerr_mute" msgid="1974781923723235953">"डिव्हाइस रीस्टार्ट होईपर्यंत म्यूट करा"</string> <string name="aerr_wait" msgid="3199956902437040261">"प्रतीक्षा करा"</string> - <string name="aerr_close_app" msgid="3269334853724920302">"अॅप बंद करा"</string> + <string name="aerr_close_app" msgid="3269334853724920302">"अॅप बंद करा"</string> <string name="anr_title" msgid="4351948481459135709"></string> <string name="anr_activity_application" msgid="8493290105678066167">"<xliff:g id="APPLICATION">%2$s</xliff:g> प्रतिसाद देत नाही"</string> <string name="anr_activity_process" msgid="1622382268908620314">"<xliff:g id="ACTIVITY">%1$s</xliff:g> प्रतिसाद देत नाही"</string> @@ -1164,7 +1164,7 @@ <string name="report" msgid="4060218260984795706">"अहवाल द्या"</string> <string name="wait" msgid="7147118217226317732">"प्रतीक्षा करा"</string> <string name="webpage_unresponsive" msgid="3272758351138122503">"पृष्ठ प्रतिसाद न देणारे झाले आहे.\n\nतुम्ही हे बंद करू इच्छिता?"</string> - <string name="launch_warning_title" msgid="1547997780506713581">"अॅप पुनर्निर्देशित केला"</string> + <string name="launch_warning_title" msgid="1547997780506713581">"अॅप पुनर्निर्देशित केला"</string> <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> आता चालत आहे."</string> <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> मूळतः लाँच केले."</string> <string name="screen_compat_mode_scale" msgid="3202955667675944499">"स्केल"</string> @@ -1175,7 +1175,7 @@ <string name="unsupported_compile_sdk_message" msgid="4253168368781441759">"<xliff:g id="APP_NAME">%1$s</xliff:g> हे Android OS च्या विसंगत आवृत्तीसाठी तयार केले होते आणि ते अनपेक्षित पद्धतीने काम करू शकते. अॅपची अपडेट केलेली आवृत्ती उपलब्ध असू शकते."</string> <string name="unsupported_compile_sdk_show" msgid="2681877855260970231">"नेहमी दर्शवा"</string> <string name="unsupported_compile_sdk_check_update" msgid="3312723623323216101">"अपडेट आहे का ते तपासा"</string> - <string name="smv_application" msgid="3307209192155442829">"अॅप <xliff:g id="APPLICATION">%1$s</xliff:g> (प्रक्रिया <xliff:g id="PROCESS">%2$s</xliff:g>) ने तिच्या स्वयं-लागू केलेल्या StrictMode धोरणाचे उल्लंघन केले आहे."</string> + <string name="smv_application" msgid="3307209192155442829">"अॅप <xliff:g id="APPLICATION">%1$s</xliff:g> (प्रक्रिया <xliff:g id="PROCESS">%2$s</xliff:g>) ने तिच्या स्वयं-लागू केलेल्या StrictMode धोरणाचे उल्लंघन केले आहे."</string> <string name="smv_process" msgid="5120397012047462446">"<xliff:g id="PROCESS">%1$s</xliff:g> प्रक्रियेने तिच्या स्वतः-लागू केलेल्या StrictMode धोरणाचे उल्लंघन केले."</string> <string name="android_upgrading_title" product="default" msgid="7513829952443484438">"फोन अपडेट होत आहे…"</string> <string name="android_upgrading_title" product="tablet" msgid="4503169817302593560">"टॅबलेट अपडेट होत आहे…"</string> @@ -1186,7 +1186,7 @@ <string name="android_upgrading_fstrim" msgid="8036718871534640010">"संचयन ऑप्टिमाइझ करत आहे."</string> <string name="android_upgrading_notification_title" product="default" msgid="1511552415039349062">"सिस्टम अपडेट संपत आहे…"</string> <string name="app_upgrading_toast" msgid="3008139776215597053">"<xliff:g id="APPLICATION">%1$s</xliff:g> श्रेणीसुधारित करत आहे…"</string> - <string name="android_upgrading_apk" msgid="7904042682111526169">"<xliff:g id="NUMBER_1">%2$d</xliff:g> पैकी <xliff:g id="NUMBER_0">%1$d</xliff:g> अॅप ऑप्टिमाइझ करत आहे."</string> + <string name="android_upgrading_apk" msgid="7904042682111526169">"<xliff:g id="NUMBER_1">%2$d</xliff:g> पैकी <xliff:g id="NUMBER_0">%1$d</xliff:g> अॅप ऑप्टिमाइझ करत आहे."</string> <string name="android_preparing_apk" msgid="8162599310274079154">"<xliff:g id="APPNAME">%1$s</xliff:g> तयार करत आहे."</string> <string name="android_upgrading_starting_apps" msgid="451464516346926713">"अॅप्स प्रारंभ करत आहे."</string> <string name="android_upgrading_complete" msgid="1405954754112999229">"बूट समाप्त होत आहे."</string> @@ -1288,7 +1288,7 @@ <string name="wifi_p2p_frequency_conflict_message" product="default" msgid="7363907213787469151">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> वर फोन कनेक्ट केलेला असताना तो वाय-फाय वरून तात्पुरता डिस्कनेक्ट केला जाईल"</string> <string name="select_character" msgid="3365550120617701745">"वर्ण घाला"</string> <string name="sms_control_title" msgid="7296612781128917719">"SMS मेसेज पाठवत आहे"</string> - <string name="sms_control_message" msgid="3867899169651496433">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> मोठ्या संख्येने SMS मेसेज पाठवत आहे. तुम्ही या अॅप ला मेसेज पाठविणे सुरु ठेवण्याची अनुमती देऊ इच्छिता?"</string> + <string name="sms_control_message" msgid="3867899169651496433">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> मोठ्या संख्येने SMS मेसेज पाठवत आहे. तुम्ही या अॅप ला मेसेज पाठविणे सुरु ठेवण्याची अनुमती देऊ इच्छिता?"</string> <string name="sms_control_yes" msgid="3663725993855816807">"अनुमती द्या"</string> <string name="sms_control_no" msgid="625438561395534982">"नकार द्या"</string> <string name="sms_short_code_confirm_message" msgid="1645436466285310855">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> हा <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>वर एक मेसेज पाठवू इच्छितो."</string> @@ -1445,7 +1445,7 @@ <string name="no_file_chosen" msgid="6363648562170759465">"फाईल निवडली नाही"</string> <string name="reset" msgid="2448168080964209908">"रीसेट करा"</string> <string name="submit" msgid="1602335572089911941">"सबमिट करा"</string> - <string name="car_mode_disable_notification_title" msgid="5704265646471239078">"ड्रायव्हिंग अॅप चालू आहे"</string> + <string name="car_mode_disable_notification_title" msgid="5704265646471239078">"ड्रायव्हिंग अॅप चालू आहे"</string> <string name="car_mode_disable_notification_message" msgid="7647248420931129377">"ड्रायव्हिंग अॅपमधून बाहेर पाडण्यासाठी टॅप करा."</string> <string name="tethered_notification_title" msgid="3146694234398202601">"टेदरिंग किंवा हॉटस्पॉट सक्रिय"</string> <string name="tethered_notification_message" msgid="2113628520792055377">"सेट करण्यासाठी टॅप करा."</string> @@ -1774,7 +1774,7 @@ <string name="package_deleted_device_owner" msgid="2307122077550236438">"आपल्या प्रशासकाने हटवले"</string> <string name="battery_saver_description_with_learn_more" msgid="6323937147992667707">"बॅटरी लाइफ वाढवण्यासाठी, बॅटरी सेव्हर काही डिव्हाइस वैशिष्ट्ये बंद करते आणि अॅप्सना प्रतिबंधित करते. "<annotation id="url">"अधिक जाणून घ्या"</annotation></string> <string name="battery_saver_description" msgid="769989536172631582">"बॅटरी लाइफ वाढवण्यासाठी, बॅटरी सेव्हर काही वैशिष्ट्ये बंद करते आणि अॅप्स प्रतिबंधित करते."</string> - <string name="data_saver_description" msgid="6015391409098303235">"डेटा वापर कमी करण्यात मदत करण्यासाठी, डेटा सर्व्हर काही अॅप्सना पार्श्वभूमीमध्ये डेटा पाठविण्यास किंवा प्राप्त करण्यास प्रतिबंधित करतो. तुम्ही सध्या वापरत असलेला अॅप डेटामध्ये प्रवेश करू शकतो परंतु तसे तो खूप कमी वेळा करू शकतो. याचा अर्थ, उदाहरणार्थ, तुम्ही इमेज टॅप करेपर्यंत त्या प्रदर्शित करणार नाहीत असा असू शकतो."</string> + <string name="data_saver_description" msgid="6015391409098303235">"डेटा वापर कमी करण्यात मदत करण्यासाठी, डेटा सर्व्हर काही अॅप्सना पार्श्वभूमीमध्ये डेटा पाठविण्यास किंवा प्राप्त करण्यास प्रतिबंधित करतो. तुम्ही सध्या वापरत असलेला अॅप डेटामध्ये प्रवेश करू शकतो परंतु तसे तो खूप कमी वेळा करू शकतो. याचा अर्थ, उदाहरणार्थ, तुम्ही इमेज टॅप करेपर्यंत त्या प्रदर्शित करणार नाहीत असा असू शकतो."</string> <string name="data_saver_enable_title" msgid="4674073932722787417">"डेटा बचतकर्ता चालू करायचा?"</string> <string name="data_saver_enable_button" msgid="7147735965247211818">"चालू करा"</string> <plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="4367877408072000848"> @@ -1861,16 +1861,16 @@ <string name="language_picker_section_all" msgid="3097279199511617537">"सर्व भाषा"</string> <string name="region_picker_section_all" msgid="8966316787153001779">"सर्व प्रदेश"</string> <string name="locale_search_menu" msgid="2560710726687249178">"शोध"</string> - <string name="app_suspended_title" msgid="2075071241147969611">"अॅप उपलब्ध नाही"</string> + <string name="app_suspended_title" msgid="2075071241147969611">"अॅप उपलब्ध नाही"</string> <string name="app_suspended_default_message" msgid="123166680425711887">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> आत्ता उपलब्ध नाही. हे <xliff:g id="APP_NAME_1">%2$s</xliff:g> कडून व्यवस्थापित केले जाते."</string> <string name="app_suspended_more_details" msgid="1131804827776778187">"अधिक जाणून घ्या"</string> <string name="work_mode_off_title" msgid="1118691887588435530">"कार्य प्रोफाइल चालू ठेवायची?"</string> <string name="work_mode_off_message" msgid="5130856710614337649">"तुमची कार्य अॅप्स, सूचना, डेटा आणि अन्य कार्य प्रोफाइल वैशिष्ट्ये चालू केली जातील"</string> <string name="work_mode_turn_on" msgid="2062544985670564875">"चालू करा"</string> - <string name="deprecated_target_sdk_message" msgid="1449696506742572767">"हे अॅप Android च्या जुन्या आवृत्ती साठी तयार करण्यात आले होते आणि योग्यरितीने कार्य करू शकणार नाही. अपडेट आहेत का ते तपासून पाहा, किंवा डेव्हलपरशी संपर्क साधण्याचा प्रयत्न करा."</string> + <string name="deprecated_target_sdk_message" msgid="1449696506742572767">"हे अॅप Android च्या जुन्या आवृत्ती साठी तयार करण्यात आले होते आणि योग्यरितीने कार्य करू शकणार नाही. अपडेट आहेत का ते तपासून पाहा, किंवा डेव्हलपरशी संपर्क साधण्याचा प्रयत्न करा."</string> <string name="deprecated_target_sdk_app_store" msgid="5032340500368495077">"अपडेट आहे का ते तपासा"</string> <string name="new_sms_notification_title" msgid="8442817549127555977">"आपल्याकडे नवीन मेसेज आहेत"</string> - <string name="new_sms_notification_content" msgid="7002938807812083463">"पाहण्यासाठी SMS अॅप उघडा"</string> + <string name="new_sms_notification_content" msgid="7002938807812083463">"पाहण्यासाठी SMS अॅप उघडा"</string> <string name="user_encrypted_title" msgid="9054897468831672082">"काही कार्यक्षमता मर्यादित असू शकतात"</string> <string name="user_encrypted_message" msgid="4923292604515744267">"अनलॉक करण्यासाठी टॅप करा"</string> <string name="user_encrypted_detail" msgid="5708447464349420392">"वापरकर्ता डेटा लॉक केला"</string> @@ -1880,7 +1880,7 @@ <string name="usb_mtp_launch_notification_description" msgid="8541876176425411358">"फायली पाहण्यासाठी टॅप करा"</string> <string name="pin_target" msgid="3052256031352291362">"पिन"</string> <string name="unpin_target" msgid="3556545602439143442">"अनपिन करा"</string> - <string name="app_info" msgid="6856026610594615344">"अॅप माहिती"</string> + <string name="app_info" msgid="6856026610594615344">"अॅप माहिती"</string> <string name="negative_duration" msgid="5688706061127375131">"−<xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="demo_starting_message" msgid="5268556852031489931">"डेमो प्रारंभ करत आहे..."</string> <string name="demo_restarting_message" msgid="952118052531642451">"डिव्हाइस रीसेट करत आहे..."</string> @@ -1945,13 +1945,13 @@ <string name="popup_window_default_title" msgid="4874318849712115433">"पॉपअप विंडो"</string> <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string> <string name="shortcut_restored_on_lower_version" msgid="4860853725206702336">"अॅपची आवृत्ती डाउनग्रेड केली, किंवा ती या शॉर्टकटशी कंपॅटिबल नाही"</string> - <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"अॅप बॅकअप आणि रिस्टोअर करण्यास सपोर्ट देत नसल्यामुळे शॉर्टकट रिस्टोअर करू शकलो नाही"</string> - <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"अॅप स्वाक्षरी न जुळल्यामुळे शॉर्टकट रिस्टोअर करू शकलो नाही"</string> + <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"अॅप बॅकअप आणि रिस्टोअर करण्यास सपोर्ट देत नसल्यामुळे शॉर्टकट रिस्टोअर करू शकलो नाही"</string> + <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"अॅप स्वाक्षरी न जुळल्यामुळे शॉर्टकट रिस्टोअर करू शकलो नाही"</string> <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"शॉर्टकट रिस्टोअर करू शकलो नाही"</string> <string name="shortcut_disabled_reason_unknown" msgid="5276016910284687075">"शॉर्टकट बंद केलेला आहे"</string> <string name="harmful_app_warning_uninstall" msgid="4837672735619532931">"अनइंस्टॉल करा"</string> <string name="harmful_app_warning_open_anyway" msgid="596432803680914321">"तरीही उघडा"</string> - <string name="harmful_app_warning_title" msgid="8982527462829423432">"हानिकारक अॅप आढळला"</string> + <string name="harmful_app_warning_title" msgid="8982527462829423432">"हानिकारक अॅप आढळला"</string> <string name="slices_permission_request" msgid="8484943441501672932">"<xliff:g id="APP_0">%1$s</xliff:g> ला <xliff:g id="APP_2">%2$s</xliff:g> चे तुकडे दाखवायचे आहेत"</string> <string name="screenshot_edit" msgid="7867478911006447565">"संपादित करा"</string> <string name="volume_dialog_ringer_guidance_vibrate" msgid="8902050240801159042">"कॉल आणि सूचनांवर व्हायब्रेट होईल"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 39afd744c415..a3da75ea0ed3 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -1083,24 +1083,24 @@ <string name="deleteText" msgid="6979668428458199034">"ဖျက်ရန်"</string> <string name="inputMethod" msgid="1653630062304567879">"ထည့်သွင်းရန်နည်းလမ်း"</string> <string name="editTextMenuTitle" msgid="4909135564941815494">"စာတို လုပ်ဆောင်ချက်"</string> - <string name="email" msgid="4560673117055050403">"အီးမေးလ်"</string> + <string name="email" msgid="4560673117055050403">"အီးမေးလ်ပို့ရန်"</string> <string name="email_desc" msgid="3638665569546416795">"ရွေးထားသည့် လိပ်စာသို့ အီးမေးလ်ပို့ရန်"</string> <string name="dial" msgid="1253998302767701559">"ခေါ်ဆိုရန်"</string> <string name="dial_desc" msgid="6573723404985517250">"ရွေးထားသည့် ဖုန်းနံပါတ်ကို ခေါ်ရန်"</string> - <string name="map" msgid="5441053548030107189">"မြေပုံ"</string> + <string name="map" msgid="5441053548030107189">"မြေပုံဖွင့်ရန်"</string> <string name="map_desc" msgid="1836995341943772348">"ရွေးထားသည့် လိပ်စာကို ရှာဖွေရန်"</string> <string name="browse" msgid="1245903488306147205">"ဖွင့်ရန်"</string> <string name="browse_desc" msgid="8220976549618935044">"ရွေးထားသည့် URL ကို ဖွင့်ရန်"</string> - <string name="sms" msgid="4560537514610063430">"SMS"</string> + <string name="sms" msgid="4560537514610063430">"SMS ပို့ရန်"</string> <string name="sms_desc" msgid="7526588350969638809">"ရွေးထားသည့် ဖုန်းနံပါတ်ကို မက်ဆေ့ဂျ်ပို့ရန်"</string> <string name="add_contact" msgid="7867066569670597203">"ထည့်ရန်"</string> <string name="add_contact_desc" msgid="4830217847004590345">"အဆက်အသွယ်များသို့ ထည့်ရန်"</string> <string name="view_calendar" msgid="979609872939597838">"ကြည့်ရန်"</string> <string name="view_calendar_desc" msgid="5828320291870344584">"ရွေးထားသည့် အချိန်ကို ပြက္ခဒိန်တွင် ကြည့်ရန်"</string> - <string name="add_calendar_event" msgid="1953664627192056206">"အချိန်ဇယား"</string> + <string name="add_calendar_event" msgid="1953664627192056206">"အစီအစဉ်ထည့်ရန်"</string> <string name="add_calendar_event_desc" msgid="4326891793260687388">"ရွေးထားသည့်အချိန်အတွက် အစီအစဉ်ပြုလုပ်ရန်"</string> <string name="view_flight" msgid="7691640491425680214">"ရှာရန်"</string> - <string name="view_flight_desc" msgid="3876322502674253506">"ရွေးထားသည့် လေယာဉ်ခရီးစဉ်ကို ရှာဖွေရန်"</string> + <string name="view_flight_desc" msgid="3876322502674253506">"ရွေးထားသည့် လေယာဉ်ခရီးစဉ်ကို ရှာရန်"</string> <string name="translate" msgid="9218619809342576858">"ဘာသာပြန်ရန်"</string> <string name="translate_desc" msgid="4502367770068777202">"ရွေးထားသောစာသားကို ဘာသာပြန်ရန်"</string> <string name="define" msgid="7394820043869954211">"အဓိပ္ပာယ်ဖွင့်ဆိုရန်"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index f606d77f28a9..30cd600f734b 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1265,7 +1265,7 @@ </string-array> <string name="network_switch_type_name_unknown" msgid="4552612897806660656">"en ukjent nettverkstype"</string> <string name="wifi_watchdog_network_disabled" msgid="7904214231651546347">"Kan ikke koble til Wi-Fi"</string> - <string name="wifi_watchdog_network_disabled_detailed" msgid="4917472096696322767">" har en dårlig Internett-tilkobling."</string> + <string name="wifi_watchdog_network_disabled_detailed" msgid="4917472096696322767">" har en dårlig internettilkobling."</string> <string name="wifi_connect_alert_title" msgid="8455846016001810172">"Vil du tillat tilkoblingen?"</string> <string name="wifi_connect_alert_message" msgid="6451273376815958922">"Appen %1$s vil koble til Wi-Fi-nettverket %2$s"</string> <string name="wifi_connect_default_application" msgid="7143109390475484319">"En app"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index a6bd007564db..2ff9fde39b49 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -247,7 +247,7 @@ <string name="global_action_settings" msgid="1756531602592545966">"Instellingen"</string> <string name="global_action_assist" msgid="3892832961594295030">"Helpen"</string> <string name="global_action_voice_assist" msgid="7751191495200504480">"Spraakassistent"</string> - <string name="global_action_lockdown" msgid="1099326950891078929">"Afgesloten"</string> + <string name="global_action_lockdown" msgid="1099326950891078929">"Lockdown"</string> <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999 +"</string> <string name="notification_hidden_text" msgid="6351207030447943784">"Nieuwe melding"</string> <string name="notification_channel_virtual_keyboard" msgid="6969925135507955575">"Virtueel toetsenbord"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 1d1f3e7ae784..e3fe527b1afe 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -1083,7 +1083,7 @@ <string name="deleteText" msgid="6979668428458199034">"ਮਿਟਾਓ"</string> <string name="inputMethod" msgid="1653630062304567879">"ਇਨਪੁੱਟ ਵਿਧੀ"</string> <string name="editTextMenuTitle" msgid="4909135564941815494">"ਟੈਕਸਟ ਕਿਰਿਆਵਾਂ"</string> - <string name="email" msgid="4560673117055050403">"ਈਮੇਲ ਕਰੋ"</string> + <string name="email" msgid="4560673117055050403">"ਈਮੇਲ ਖੋਲ੍ਹੋ"</string> <string name="email_desc" msgid="3638665569546416795">"ਚੁਣੇ ਹੋਏ ਪਤੇ \'ਤੇ ਈਮੇਲ ਭੇਜੋ"</string> <string name="dial" msgid="1253998302767701559">"ਕਾਲ ਕਰੋ"</string> <string name="dial_desc" msgid="6573723404985517250">"ਚੁਣੇ ਗਏ ਫ਼ੋਨ ਨੰਬਰ \'ਤੇ ਕਾਲ ਕਰੋ"</string> @@ -1097,11 +1097,11 @@ <string name="add_contact_desc" msgid="4830217847004590345">"ਸੰਪਰਕਾਂ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ"</string> <string name="view_calendar" msgid="979609872939597838">"ਦੇਖੋ"</string> <string name="view_calendar_desc" msgid="5828320291870344584">"ਕੈਲੰਡਰ ਵਿੱਚ ਚੁਣਿਆ ਗਿਆ ਸਮਾਂ ਦੇਖੋ"</string> - <string name="add_calendar_event" msgid="1953664627192056206">"ਸਮਾਂ-ਸੂਚੀ"</string> + <string name="add_calendar_event" msgid="1953664627192056206">"ਸਮਾਂ-ਸੂਚੀ ਬਣਾਓ"</string> <string name="add_calendar_event_desc" msgid="4326891793260687388">"ਚੁਣੇ ਗਏ ਸਮੇਂ ਲਈ ਇਵੈਂਟ ਦੀ ਸਮਾਂ-ਸੂਚੀ ਬਣਾਓ"</string> <string name="view_flight" msgid="7691640491425680214">"ਟਰੈਕ ਕਰੋ"</string> <string name="view_flight_desc" msgid="3876322502674253506">"ਚੁਣੀ ਗਈ ਉਡਾਣ ਨੂੰ ਟਰੈਕ ਕਰੋ"</string> - <string name="translate" msgid="9218619809342576858">"ਅਨੁਵਾਦ"</string> + <string name="translate" msgid="9218619809342576858">"ਅਨੁਵਾਦ ਕਰੋ"</string> <string name="translate_desc" msgid="4502367770068777202">"ਚੋਣਵੀਂ ਲਿਖਤ ਦਾ ਅਨੁਵਾਦ ਕਰੋ"</string> <string name="define" msgid="7394820043869954211">"ਪਰਿਭਾਸ਼ਾ ਦਿਓ"</string> <string name="define_desc" msgid="7910883642444919726">"ਚੋਣਵੀਂ ਲਿਖਤ ਦਾ ਅਨੁਵਾਦ ਕਰੋ"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 9a70f0b4e4b9..0790e904ec18 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -1123,26 +1123,26 @@ <string name="deleteText" msgid="6979668428458199034">"Izbriši"</string> <string name="inputMethod" msgid="1653630062304567879">"Način vnosa"</string> <string name="editTextMenuTitle" msgid="4909135564941815494">"Besedilna dejanja"</string> - <string name="email" msgid="4560673117055050403">"E-pošta"</string> + <string name="email" msgid="4560673117055050403">"Pošlji e-pošto"</string> <string name="email_desc" msgid="3638665569546416795">"Pošlji na izbrani naslov"</string> <string name="dial" msgid="1253998302767701559">"Pokliči"</string> <string name="dial_desc" msgid="6573723404985517250">"Pokliči izbrano telefonsko številko"</string> - <string name="map" msgid="5441053548030107189">"Zemljevid"</string> + <string name="map" msgid="5441053548030107189">"Odpri zemljevid"</string> <string name="map_desc" msgid="1836995341943772348">"Poišči izbrani naslov na zemljevidu"</string> <string name="browse" msgid="1245903488306147205">"Odpri"</string> <string name="browse_desc" msgid="8220976549618935044">"Odpri izbrani URL"</string> - <string name="sms" msgid="4560537514610063430">"Sporočilo"</string> + <string name="sms" msgid="4560537514610063430">"Pošlji SMS"</string> <string name="sms_desc" msgid="7526588350969638809">"Pošlji sporočilo na izbrano telefonsko številko"</string> <string name="add_contact" msgid="7867066569670597203">"Dodaj"</string> <string name="add_contact_desc" msgid="4830217847004590345">"Dodaj med stike"</string> - <string name="view_calendar" msgid="979609872939597838">"Ogled"</string> - <string name="view_calendar_desc" msgid="5828320291870344584">"Ogled izbranega časa v koledarju"</string> + <string name="view_calendar" msgid="979609872939597838">"Prikaži"</string> + <string name="view_calendar_desc" msgid="5828320291870344584">"Prikaži izbrani čas v koledarju"</string> <string name="add_calendar_event" msgid="1953664627192056206">"Dodaj na razpored"</string> <string name="add_calendar_event_desc" msgid="4326891793260687388">"Razporedi dogodek na izbrano uro"</string> - <string name="view_flight" msgid="7691640491425680214">"Sledenje"</string> + <string name="view_flight" msgid="7691640491425680214">"Sledi"</string> <string name="view_flight_desc" msgid="3876322502674253506">"Sledi izbranemu letu"</string> <string name="translate" msgid="9218619809342576858">"Prevedi"</string> - <string name="translate_desc" msgid="4502367770068777202">"Prevod izbranega besedila"</string> + <string name="translate_desc" msgid="4502367770068777202">"Prevedi izbrano besedilo"</string> <string name="define" msgid="7394820043869954211">"Opredeli"</string> <string name="define_desc" msgid="7910883642444919726">"Opredeli izbrano besedilo"</string> <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Prostor za shranjevanje bo pošel"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 25171b835434..51ac513b8885 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1096,10 +1096,10 @@ <string name="add_contact" msgid="7867066569670597203">"Ongeza"</string> <string name="add_contact_desc" msgid="4830217847004590345">"Ongeza kwenye anwani"</string> <string name="view_calendar" msgid="979609872939597838">"Angalia"</string> - <string name="view_calendar_desc" msgid="5828320291870344584">"Angalia wakati uliyochagua katika kalenda"</string> - <string name="add_calendar_event" msgid="1953664627192056206">"Ratiba"</string> + <string name="view_calendar_desc" msgid="5828320291870344584">"Angalia wakati uliochagua katika kalenda"</string> + <string name="add_calendar_event" msgid="1953664627192056206">"Ratibu"</string> <string name="add_calendar_event_desc" msgid="4326891793260687388">"Ratibu tukio la wakati uliochagua"</string> - <string name="view_flight" msgid="7691640491425680214">"Toleo"</string> + <string name="view_flight" msgid="7691640491425680214">"Fuatilia"</string> <string name="view_flight_desc" msgid="3876322502674253506">"Fuatilia ndege uliyochagua"</string> <string name="translate" msgid="9218619809342576858">"Tafsiri"</string> <string name="translate_desc" msgid="4502367770068777202">"Tafsiri maandishi yaliyochaguliwa"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 492bad58842a..f59cfcb427a1 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -1091,7 +1091,7 @@ <string name="map_desc" msgid="1836995341943772348">"搵出指定地址"</string> <string name="browse" msgid="1245903488306147205">"開啟"</string> <string name="browse_desc" msgid="8220976549618935044">"打開指定網址"</string> - <string name="sms" msgid="4560537514610063430">"短訊"</string> + <string name="sms" msgid="4560537514610063430">"發短訊"</string> <string name="sms_desc" msgid="7526588350969638809">"Send 短訊去指定電話號碼"</string> <string name="add_contact" msgid="7867066569670597203">"新增"</string> <string name="add_contact_desc" msgid="4830217847004590345">"加入通訊錄"</string> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 089c59f3a09f..ab4bd0534025 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -268,6 +268,12 @@ <!-- Additional flag from base permission type: this permission can be automatically granted to the system default text classifier --> <flag name="textClassifier" value="0x10000" /> + <!-- Additional flag from base permission type: this permission will be granted to the + wellbeing app, as defined by the OEM. --> + <flag name="wellbeing" value="0x20000" /> + <!-- Additional flag from base permission type: this permission can be automatically + granted to the document manager --> + <flag name="documenter" value="0x40000" /> </attr> <!-- Flags indicating more context for a permission group. --> @@ -1394,6 +1400,29 @@ <attr name="usesNonSdkApi" format="boolean" /> + <!-- Specify the type of foreground service. Apps targeting API + {@link android.os.Build.VERSION_CODES#Q} or later must specify foreground service type, + otherwise a SecurityException is thrown when + {@link android.app.Service#startForeground(int, Notification)} on this service is called. + --> + <attr name="foregroundServiceType"> + <!-- Data (photo, file, account) upload/download, backup/restore, import/export, fetch, + transfer over network between device and cloud. --> + <enum name="sync" value="1" /> + <!-- Music, video, news or other media play. --> + <enum name="mediaPlay" value="2" /> + <!-- Ongoing phone call or video conference. --> + <enum name="phoneCall" value="3" /> + <!-- GPS, map, navigation location update. --> + <enum name="location" value="4" /> + <!-- Auto, bluetooth, TV or other devices connection, monitoring and interaction. --> + <enum name="deviceCompanion" value="5" /> + <!-- Process that should not be interrupted, including installation, setup, photo + compression etc. --> + <enum name="ongoingProcess" value="6" /> + </attr> + + <!-- The <code>manifest</code> tag is the root of an <code>AndroidManifest.xml</code> file, describing the contents of an Android package (.apk) file. One @@ -2242,6 +2271,8 @@ recommended to measure memory usage under typical workloads to determine whether it makes sense to use this flag. --> <attr name="useAppZygote" format="boolean" /> + <!-- If this is a foreground service, specify its category. --> + <attr name="foregroundServiceType" /> </declare-styleable> <!-- The <code>receiver</code> tag declares an diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 499278cf5a16..97a21a55f67f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -222,6 +222,11 @@ so that applications can still use their own mechanisms. --> <bool name="config_enableAutoPowerModes">false</bool> + <!-- Whether (if true) this is a kind of device that can be moved around (eg. phone/laptop), + or (if false) something for which movement is either not measurable or should not count + toward power states (eg. tv/soundbar). --> + <bool name="config_autoPowerModeUseMotionSensor">true</bool> + <!-- The threshold angle for any motion detection in auto-power save modes. In hundreths of a degree. --> <integer name="config_autoPowerModeThresholdAngle">200</integer> @@ -3349,14 +3354,29 @@ See android.view.textclassifier.TextClassificationManager. --> <string name="config_defaultTextClassifierPackage" translatable="false"></string> + + <!-- The package name for the default wellbeing app. + This package must be trusted, as it has the permissions to control other applications + on the device. + Example: "com.android.wellbeing" + --> + <string name="config_defaultWellbeingPackage" translatable="false"></string> + + <!-- The package name for the system's content capture service. + This service must be trusted, as it can be activated without explicit consent of the user. + If no service with the specified name exists on the device, content capture will be + disabled. + Example: "com.android.contentcapture/.ContentcaptureService" + --> + <string name="config_defaultContentCaptureService" translatable="false"></string> - <!-- The package name for the system's smart suggestion service. + <!-- The package name for the system's augmented autofill service. This service must be trusted, as it can be activated without explicit consent of the user. - If no service with the specified name exists on the device, content capture and - smart suggestions will be disabled. - Example: "com.android.intelligence/.SmartSuggestionsService" + If no service with the specified name exists on the device, augmented autofill wil be + disabled. + Example: "com.android.augmentedautofill/.AugmentedAutofillService" --> - <string name="config_defaultSmartSuggestionsService" translatable="false"></string> + <string name="config_defaultAugmentedAutofillService" translatable="false"></string> <!-- Whether the device uses the default focus highlight when focus state isn't specified. --> <bool name="config_useDefaultFocusHighlight">true</bool> @@ -3577,4 +3597,9 @@ <!-- Component name for default assistant on this device --> <string name="config_defaultAssistantComponentName">#+UNSET</string> + <!-- Class name for the InputEvent compatibility processor override. + Empty string means use the default compatibility processor + (android.view.InputEventCompatProcessor). --> + <string name="config_inputEventCompatProcessorOverrideClassName" translatable="false"></string> + </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 5e8af62d1da9..d480121fc998 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2929,6 +2929,7 @@ <public name="dataUsedForMonetization" /> <public name="dataRetentionTime" /> <public name="selectionDividerHeight" /> + <public name="foregroundServiceType" /> </public-group> <public-group type="drawable" first-id="0x010800b4"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a33f6b2fbf9c..cab01f9c027f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1215,6 +1215,15 @@ <string name="permdesc_manageOwnCalls">Allows the app to route its calls through the system in order to improve the calling experience.</string> + <!-- Title of an application permission. When granted the app is allowed to be enabled as + a companion app. [CHAR LIMIT=NONE]--> + <string name="permlab_callCompanionApp">see and control calls through the system.</string> + <!-- Description of an application permission. When granted the app is allowed to be enabled as + a companion app. [CHAR LIMIT=NONE]--> + <string name="permdesc_callCompanionApp">Allows the app to see and control ongoing calls on the + device. This includes information such as call numbers for calls and the state of the + calls.</string> + <!-- Title of an application permission. When granted the user is giving access to a third party app to continue a call which originated in another app. For example, the user could be in a voice call over their carrier's mobile network, and a third party video @@ -1397,6 +1406,16 @@ re-enables the keylock when the call is finished.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> + <string name="permlab_getAndRequestScreenLockComplexity">get and request screen lock complexity</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> + <string name="permdesc_getAndRequestScreenLockComplexity">Allows the app to learn the screen + lock complexity level (high, medium, low or none), which indicates the possible range of + length and type of the screen lock. The app can also suggest to users that they update the + screen lock to a certain level but users can freely ignore and navigate away. Note that the + screen lock is not stored in plaintext so the app does not know the exact password. + </string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> <string name="permlab_useBiometric">use biometric hardware</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] --> <string name="permdesc_useBiometric">Allows the app to use biometric hardware for authentication</string> @@ -3323,6 +3342,15 @@ <!-- Notification action name for opening the wifi picker, showing the user all the nearby networks. --> <string name="wifi_available_action_all_networks">All networks</string> + <!-- Notification title for a connection to a app suggested wireless network.--> + <string name="wifi_suggestion_title">Connected to Wi\u2011Fi network proposed by <xliff:g id="name" example="App123">%s</xliff:g></string> + <!-- Notification content for a connection to a app suggested wireless network.--> + <string name="wifi_suggestion_content">Do you want to let <xliff:g id="name" example="App123">%s</xliff:g> propose networks for you?</string> + <!-- Notification action for allowing app specified in the notification body.--> + <string name="wifi_suggestion_action_allow_app">Yes</string> + <!-- Notification action for disallowing app specified in the notification body.--> + <string name="wifi_suggestion_action_disallow_app">No</string> + <!--Notification title for Wi-Fi Wake onboarding. This is displayed the first time a user disables Wi-Fi with the feature enabled. --> <string name="wifi_wakeup_onboarding_title">Wi\u2011Fi will turn on automatically</string> <!--Notification subtext for Wi-Fi Wake onboarding.--> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6854a84e950a..161e41681486 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -265,6 +265,7 @@ <java-symbol type="integer" name="config_autoPowerModeAnyMotionSensor" /> <java-symbol type="bool" name="config_autoPowerModePreferWristTilt" /> <java-symbol type="bool" name="config_autoPowerModePrefetchLocation" /> + <java-symbol type="bool" name="config_autoPowerModeUseMotionSensor" /> <java-symbol type="bool" name="config_enable_emergency_call_while_sim_locked" /> <java-symbol type="bool" name="config_enable_puk_unlock_screen" /> <java-symbol type="bool" name="config_disableLockscreenByDefault" /> @@ -301,6 +302,7 @@ <java-symbol type="bool" name="config_enableWallpaperService" /> <java-symbol type="bool" name="config_checkWallpaperAtBoot" /> <java-symbol type="string" name="config_wallpaperManagerServiceName" /> + <java-symbol type="string" name="config_inputEventCompatProcessorOverrideClassName" /> <java-symbol type="bool" name="config_enableUpdateableTimeZoneRules" /> <java-symbol type="bool" name="config_timeZoneRulesUpdateTrackingEnabled" /> <java-symbol type="string" name="config_timeZoneRulesUpdaterPackage" /> @@ -1988,6 +1990,10 @@ <java-symbol type="string" name="wifi_available_content_failed_to_connect" /> <java-symbol type="string" name="wifi_available_action_connect" /> <java-symbol type="string" name="wifi_available_action_all_networks" /> + <java-symbol type="string" name="wifi_suggestion_title" /> + <java-symbol type="string" name="wifi_suggestion_content" /> + <java-symbol type="string" name="wifi_suggestion_action_allow_app" /> + <java-symbol type="string" name="wifi_suggestion_action_disallow_app" /> <java-symbol type="string" name="wifi_wakeup_onboarding_title" /> <java-symbol type="string" name="wifi_wakeup_onboarding_subtext" /> <java-symbol type="string" name="wifi_wakeup_onboarding_action_disable" /> @@ -3265,7 +3271,9 @@ <java-symbol type="string" name="notification_channel_do_not_disturb" /> <java-symbol type="string" name="config_defaultAutofillService" /> <java-symbol type="string" name="config_defaultTextClassifierPackage" /> - <java-symbol type="string" name="config_defaultSmartSuggestionsService" /> + <java-symbol type="string" name="config_defaultWellbeingPackage" /> + <java-symbol type="string" name="config_defaultContentCaptureService" /> + <java-symbol type="string" name="config_defaultAugmentedAutofillService" /> <java-symbol type="string" name="notification_channel_foreground_service" /> <java-symbol type="string" name="foreground_service_app_in_background" /> diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java index d289f1f5defc..9b5b725a3bed 100644 --- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java +++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java @@ -16,9 +16,16 @@ package android.app.admin; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import android.app.admin.PasswordMetrics.PasswordComplexityBucket; import android.os.Parcel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -164,4 +171,126 @@ public class PasswordMetricsTest { } + + @Test + public void testConstructQuality() { + PasswordMetrics expected = new PasswordMetrics(); + expected.quality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; + + PasswordMetrics actual = new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX); + + assertEquals(expected, actual); + } + + @Test + public void testDetermineComplexity_none() { + assertEquals(PASSWORD_COMPLEXITY_NONE, + PasswordMetrics.computeForPassword("").determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowSomething() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + new PasswordMetrics(PASSWORD_QUALITY_SOMETHING).determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowNumeric() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + PasswordMetrics.computeForPassword("1234").determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowNumericComplex() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + PasswordMetrics.computeForPassword("124").determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowAlphabetic() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + PasswordMetrics.computeForPassword("a!").determineComplexity()); + } + + @Test + public void testDetermineComplexity_lowAlphanumeric() { + assertEquals(PASSWORD_COMPLEXITY_LOW, + PasswordMetrics.computeForPassword("a!1").determineComplexity()); + } + + @Test + public void testDetermineComplexity_mediumNumericComplex() { + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, + PasswordMetrics.computeForPassword("1238").determineComplexity()); + } + + @Test + public void testDetermineComplexity_mediumAlphabetic() { + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, + PasswordMetrics.computeForPassword("ab!c").determineComplexity()); + } + + @Test + public void testDetermineComplexity_mediumAlphanumeric() { + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, + PasswordMetrics.computeForPassword("ab!1").determineComplexity()); + } + + @Test + public void testDetermineComplexity_highNumericComplex() { + assertEquals(PASSWORD_COMPLEXITY_HIGH, + PasswordMetrics.computeForPassword("12389647!").determineComplexity()); + } + + @Test + public void testDetermineComplexity_highAlphabetic() { + assertEquals(PASSWORD_COMPLEXITY_HIGH, + PasswordMetrics.computeForPassword("alphabetic!").determineComplexity()); + } + + @Test + public void testDetermineComplexity_highAlphanumeric() { + assertEquals(PASSWORD_COMPLEXITY_HIGH, + PasswordMetrics.computeForPassword("alphanumeric123!").determineComplexity()); + } + + @Test + public void testComplexityLevelToBucket_none() { + PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( + PASSWORD_COMPLEXITY_NONE).getMetrics(); + + for (PasswordMetrics metrics : bucket) { + assertEquals(PASSWORD_COMPLEXITY_NONE, metrics.determineComplexity()); + } + } + + @Test + public void testComplexityLevelToBucket_low() { + PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( + PASSWORD_COMPLEXITY_LOW).getMetrics(); + + for (PasswordMetrics metrics : bucket) { + assertEquals(PASSWORD_COMPLEXITY_LOW, metrics.determineComplexity()); + } + } + + @Test + public void testComplexityLevelToBucket_medium() { + PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( + PASSWORD_COMPLEXITY_MEDIUM).getMetrics(); + + for (PasswordMetrics metrics : bucket) { + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metrics.determineComplexity()); + } + } + + @Test + public void testComplexityLevelToBucket_high() { + PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( + PASSWORD_COMPLEXITY_HIGH).getMetrics(); + + for (PasswordMetrics metrics : bucket) { + assertEquals(PASSWORD_COMPLEXITY_HIGH, metrics.determineComplexity()); + } + } } diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java index 1f047f9e6d10..28aaf1e05644 100644 --- a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java +++ b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java @@ -16,18 +16,22 @@ package android.app.usage; -import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY; +import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED; import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE; import static android.app.usage.UsageEvents.Event.END_OF_DAY; +import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK; import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START; import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP; -import static android.app.usage.UsageEvents.Event.MOVE_TO_BACKGROUND; -import static android.app.usage.UsageEvents.Event.MOVE_TO_FOREGROUND; import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import android.app.usage.UsageEvents.Event; import android.os.Parcel; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -120,10 +124,10 @@ public class UsageStatsTest { left.mBeginTimeStamp = 100000; left.mTotalTimeInForeground = 10; - left.mLastForegroundActivityEventMap.put("com.test.activity1", MOVE_TO_FOREGROUND); - left.mLastForegroundActivityEventMap.put("com.test.activity2", MOVE_TO_FOREGROUND); - left.mLastForegroundServiceEventMap.put("com.test.service1", FOREGROUND_SERVICE_START); - left.mLastForegroundServiceEventMap.put("com.test.service2", FOREGROUND_SERVICE_START); + left.mActivities.put(1, Event.ACTIVITY_RESUMED); + left.mActivities.put(2, Event.ACTIVITY_RESUMED); + left.mForegroundServices.put("com.test.service1", FOREGROUND_SERVICE_START); + left.mForegroundServices.put("com.test.service2", FOREGROUND_SERVICE_START); Parcel p = Parcel.obtain(); left.writeToParcel(p, 0); @@ -133,131 +137,182 @@ public class UsageStatsTest { } @Test - public void testForegroundActivity() { + public void testActivity() { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 200000, MOVE_TO_FOREGROUND); + left.update("com.test.activity1", 200000, Event.ACTIVITY_RESUMED, 1); assertEquals(left.mLastTimeUsed, 200000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(MOVE_TO_FOREGROUND)); + assertEquals(left.mLastTimeVisible, 200000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); assertEquals(left.mLaunchCount, 1); + assertEquals(left.mTotalTimeInForeground, 0); + assertEquals(left.mTotalTimeVisible, 0); - left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND); + left.update("com.test.activity1", 350000, ACTIVITY_PAUSED, 1); assertEquals(left.mLastTimeUsed, 350000); - assertFalse(left.mLastForegroundActivityEventMap.containsKey("com.test.activity1")); + assertEquals(left.mLastTimeVisible, 350000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 350000 - 200000); + assertEquals(left.mTotalTimeVisible, 350000 - 200000); + + left.update("com.test.activity1", 400000, ACTIVITY_STOPPED, 1); + assertEquals(left.mLastTimeUsed, 350000); + assertEquals(left.mLastTimeVisible, 400000); + assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED); + assertEquals(left.mTotalTimeInForeground, 350000 - 200000); + assertEquals(left.mTotalTimeVisible, 400000 - 200000); + + left.update("com.test.activity1", 500000, ACTIVITY_DESTROYED, 1); + assertEquals(left.mLastTimeUsed, 350000); + assertEquals(left.mLastTimeVisible, 400000); + assertTrue(left.mActivities.indexOfKey(1) < 0); + assertEquals(left.mTotalTimeInForeground, 350000 - 200000); + assertEquals(left.mTotalTimeVisible, 400000 - 200000); } @Test - public void testEvent_CONTINUE_PREVIOUS_DAY() { + public void testEvent_END_OF_DAY() { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY); + left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1); assertEquals(left.mLastTimeUsed, 100000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLaunchCount, 0); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); + assertEquals(left.mLaunchCount, 1); - left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND); + left.update(null, 350000, END_OF_DAY, 0); assertEquals(left.mLastTimeUsed, 350000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); + assertEquals(left.mLastTimeVisible, 350000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); assertEquals(left.mTotalTimeInForeground, 350000 - 100000); + assertEquals(left.mTotalTimeVisible, 350000 - 100000); } @Test - public void testEvent_END_OF_DAY() { + public void testEvent_ACTIVITY_PAUSED() { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY); - assertEquals(left.mLastTimeUsed, 100000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLaunchCount, 0); + left.update("com.test.activity1", 100000, ACTIVITY_PAUSED, 1); + assertEquals(left.mLastTimeUsed, 0); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); - left.update(null, 350000, END_OF_DAY); - assertEquals(left.mLastTimeUsed, 350000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(END_OF_DAY)); - assertEquals(left.mTotalTimeInForeground, 350000 - 100000); + left.update("com.test.activity1", 200000, Event.ACTIVITY_RESUMED, 1); + assertEquals(left.mLastTimeUsed, 200000); + assertEquals(left.mLastTimeVisible, 200000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); + assertEquals(left.mTotalTimeInForeground, 0); + assertEquals(left.mTotalTimeVisible, 200000 - 100000); + + left.update("com.test.activity1", 300000, ACTIVITY_PAUSED, 1); + assertEquals(left.mLastTimeUsed, 300000); + assertEquals(left.mLastTimeVisible, 300000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); + assertEquals(left.mTotalTimeInForeground, 300000 - 200000); + assertEquals(left.mTotalTimeVisible, 300000 - 100000); + + left.update("com.test.activity1", 400000, ACTIVITY_STOPPED, 1); + assertEquals(left.mLastTimeUsed, 300000); + assertEquals(left.mLastTimeVisible, 400000); + assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED); + assertEquals(left.mTotalTimeInForeground, 300000 - 200000); + assertEquals(left.mTotalTimeVisible, 400000 - 100000); } @Test - public void testForegroundActivityEventSequence() { + public void testEvent_CHANGE_TO_INVISIBLE() { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY); + left.update("com.test.activity1", 100000, ACTIVITY_RESUMED, 1); assertEquals(left.mLastTimeUsed, 100000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLaunchCount, 0); - - left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND); - assertEquals(left.mLastTimeUsed, 350000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); - assertEquals(left.mTotalTimeInForeground, 250000 /*350000 - 100000*/); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), ACTIVITY_RESUMED); - left.update("com.test.activity1", 450000, MOVE_TO_FOREGROUND); - assertEquals(left.mLastTimeUsed, 450000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(MOVE_TO_FOREGROUND)); - assertEquals(left.mTotalTimeInForeground, 250000); - - left.update("com.test.activity1", 500000, MOVE_TO_BACKGROUND); - assertEquals(left.mLastTimeUsed, 500000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); - assertEquals(left.mTotalTimeInForeground, 250000 + 50000 /*500000 - 450000*/); + left.update("com.test.activity1", 200000, ACTIVITY_STOPPED, 1); + assertEquals(left.mLastTimeUsed, 200000); + assertEquals(left.mLastTimeVisible, 200000); + assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED); + assertEquals(left.mTotalTimeInForeground, 200000 - 100000); + assertEquals(left.mTotalTimeVisible, 200000 - 100000); + + left.update("com.test.activity1", 300000, ACTIVITY_RESUMED, 1); + assertEquals(left.mLastTimeUsed, 300000); + assertEquals(left.mLastTimeVisible, 300000); + assertEquals(left.mActivities.get(1), ACTIVITY_RESUMED); + assertEquals(left.mTotalTimeInForeground, 200000 - 100000); + assertEquals(left.mTotalTimeVisible, 200000 - 100000); } @Test - public void testForegroundActivityEventOutOfSequence() { + public void testEvent_ACTIVITY_DESTROYED() { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY); + left.update("com.test.activity1", 100000, ACTIVITY_RESUMED, 1); assertEquals(left.mLastTimeUsed, 100000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLaunchCount, 0); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), ACTIVITY_RESUMED); - left.update("com.test.activity1", 150000, MOVE_TO_FOREGROUND); - assertEquals(left.mLastTimeUsed, 150000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(MOVE_TO_FOREGROUND)); + left.update("com.test.activity1", 200000, ACTIVITY_DESTROYED, 1); + assertEquals(left.mLastTimeUsed, 200000); + assertEquals(left.mLastTimeVisible, 200000); + assertTrue(left.mActivities.indexOfKey(1) < 0); + assertEquals(left.mTotalTimeInForeground, 200000 - 100000); + assertEquals(left.mTotalTimeVisible, 200000 - 100000); + } + + @Test + public void testActivityEventOutOfOrder() { + left.mPackageName = "com.test"; + left.mBeginTimeStamp = 100000; + + left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1); + assertEquals(left.mLastTimeUsed, 100000); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); assertEquals(left.mLaunchCount, 1); - assertEquals(left.mTotalTimeInForeground, 50000 /*150000 - 100000*/); + assertEquals(left.mTotalTimeInForeground, 0); + assertEquals(left.mTotalTimeVisible, 0); - left.update("com.test.activity1", 200000, MOVE_TO_FOREGROUND); + left.update("com.test.activity1", 200000, Event.ACTIVITY_RESUMED, 1); assertEquals(left.mLastTimeUsed, 200000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(MOVE_TO_FOREGROUND)); + assertEquals(left.mLastTimeVisible, 200000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); assertEquals(left.mLaunchCount, 2); assertEquals(left.mTotalTimeInForeground, 100000); + assertEquals(left.mTotalTimeVisible, 100000 /*200000 - 100000*/); - left.update("com.test.activity1", 250000, MOVE_TO_BACKGROUND); + left.update("com.test.activity1", 250000, ACTIVITY_PAUSED, 1); assertEquals(left.mLastTimeUsed, 250000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); + assertEquals(left.mLastTimeVisible, 250000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 150000); + assertEquals(left.mTotalTimeVisible, 150000 /*250000 - 100000*/); - left.update("com.test.activity1", 300000, MOVE_TO_BACKGROUND); + left.update("com.test.activity1", 300000, ACTIVITY_PAUSED, 1); assertEquals(left.mLastTimeUsed, 250000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); + assertEquals(left.mLastTimeVisible, 300000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 150000); + assertEquals(left.mTotalTimeVisible, 200000 /*300000 - 100000*/); - left.update("com.test.activity1", 350000, MOVE_TO_FOREGROUND); + left.update("com.test.activity1", 350000, Event.ACTIVITY_RESUMED, 1); assertEquals(left.mLastTimeUsed, 350000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(MOVE_TO_FOREGROUND)); + assertEquals(left.mLastTimeVisible, 350000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); assertEquals(left.mTotalTimeInForeground, 150000); + assertEquals(left.mTotalTimeVisible, 250000 /*350000 - 100000*/); - left.update("com.test.activity1", 400000, END_OF_DAY); + left.update("com.test.activity1", 400000, END_OF_DAY, 1); assertEquals(left.mLastTimeUsed, 400000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(END_OF_DAY)); + assertEquals(left.mLastTimeVisible, 400000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); assertEquals(left.mTotalTimeInForeground, 200000); + assertEquals(left.mTotalTimeVisible, 300000 /*400000 - 100000*/); } @Test @@ -265,28 +320,41 @@ public class UsageStatsTest { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY); - left.update("com.test.activity2", 100000, CONTINUE_PREVIOUS_DAY); + left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1); + left.update("com.test.activity2", 100000, Event.ACTIVITY_RESUMED, 2); assertEquals(left.mLastTimeUsed, 100000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLaunchCount, 0); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); + assertEquals(left.mActivities.get(2), Event.ACTIVITY_RESUMED); + assertEquals(left.mLaunchCount, 2); - left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND); + left.update("com.test.activity1", 350000, ACTIVITY_PAUSED, 1); assertEquals(left.mLastTimeUsed, 350000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); + assertEquals(left.mLastTimeVisible, 350000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 250000 /*350000 - 100000*/); + assertEquals(left.mTotalTimeVisible, 250000 /*350000 - 100000*/); - left.update("com.test.activity2", 450000, MOVE_TO_BACKGROUND); + left.update("com.test.activity2", 450000, ACTIVITY_PAUSED, 2); assertEquals(left.mLastTimeUsed, 450000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"), null); + assertEquals(left.mLastTimeVisible, 450000); + assertEquals(left.mActivities.get(2), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 250000 + 100000 /*450000 - 350000*/); + assertEquals(left.mTotalTimeVisible, 250000 + 100000 /*450000 - 350000*/); + + left.update("com.test.activity1", 550000, ACTIVITY_STOPPED, 1); + assertEquals(left.mLastTimeUsed, 450000); + assertEquals(left.mLastTimeVisible, 550000); + assertEquals(left.mActivities.get(1), ACTIVITY_STOPPED); + assertEquals(left.mTotalTimeInForeground, 350000); + assertEquals(left.mTotalTimeVisible, 350000 + 100000 /*550000 - 450000*/); - left.update(null, 500000, END_OF_DAY); + left.update("com.test.activity2", 650000, ACTIVITY_STOPPED, 2); assertEquals(left.mLastTimeUsed, 450000); + assertEquals(left.mLastTimeVisible, 650000); + assertEquals(left.mActivities.get(2), ACTIVITY_STOPPED); assertEquals(left.mTotalTimeInForeground, 350000); + assertEquals(left.mTotalTimeVisible, 450000 + 100000 /*650000 - 550000*/); } @Test @@ -294,15 +362,14 @@ public class UsageStatsTest { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.service1", 200000, FOREGROUND_SERVICE_START); + left.update("com.test.service1", 200000, FOREGROUND_SERVICE_START, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 200000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(FOREGROUND_SERVICE_START)); - assertEquals(left.mLaunchCount, 0); - left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 350000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null); + assertEquals(left.mForegroundServices.get("com.test.service1"), null); assertEquals(left.mTotalTimeForegroundServiceUsed, 350000 - 200000); } @@ -311,15 +378,15 @@ public class UsageStatsTest { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.service1", 100000, CONTINUING_FOREGROUND_SERVICE); + left.update("com.test.service1", 100000, + CONTINUING_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 100000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(CONTINUING_FOREGROUND_SERVICE)); - assertEquals(left.mLaunchCount, 0); - left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 350000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null); + assertEquals(left.mForegroundServices.get("com.test.service1"), null); assertEquals(left.mTotalTimeForegroundServiceUsed, 350000 - 100000); } @@ -329,16 +396,15 @@ public class UsageStatsTest { left.mBeginTimeStamp = 100000; left.update("com.test.service1", 100000, - CONTINUING_FOREGROUND_SERVICE); + CONTINUING_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 100000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(CONTINUING_FOREGROUND_SERVICE)); - assertEquals(left.mLaunchCount, 0); - left.update(null, 350000, ROLLOVER_FOREGROUND_SERVICE); + left.update(null, 350000, ROLLOVER_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 350000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), - new Integer(ROLLOVER_FOREGROUND_SERVICE)); + assertEquals(left.mForegroundServices.get("com.test.service1"), + new Integer(CONTINUING_FOREGROUND_SERVICE)); assertEquals(left.mTotalTimeForegroundServiceUsed, 350000 - 100000); } @@ -348,27 +414,28 @@ public class UsageStatsTest { left.mBeginTimeStamp = 100000; left.update("com.test.service1", 100000, - CONTINUING_FOREGROUND_SERVICE); + CONTINUING_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 100000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(CONTINUING_FOREGROUND_SERVICE)); assertEquals(left.mLaunchCount, 0); - left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 350000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null); + assertEquals(left.mForegroundServices.get("com.test.service1"), null); assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 /*350000 - 100000*/); - left.update("com.test.service1", 450000, FOREGROUND_SERVICE_START); + left.update("com.test.service1", 450000, FOREGROUND_SERVICE_START, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 450000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(FOREGROUND_SERVICE_START)); assertEquals(left.mTotalTimeForegroundServiceUsed, 250000); - left.update("com.test.service1", 500000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service1", 500000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 500000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null); - assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 + 50000 /*500000 - 450000*/); + assertEquals(left.mForegroundServices.get("com.test.service1"), null); + assertEquals(left.mTotalTimeForegroundServiceUsed, + 250000 + 50000 /*500000 - 450000*/); } @Test @@ -377,27 +444,27 @@ public class UsageStatsTest { left.mBeginTimeStamp = 100000; left.update("com.test.service1", 100000, - CONTINUING_FOREGROUND_SERVICE); + CONTINUING_FOREGROUND_SERVICE, 0); left.update("com.test.service2", 100000, - CONTINUING_FOREGROUND_SERVICE); + CONTINUING_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 100000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(CONTINUING_FOREGROUND_SERVICE)); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"), + assertEquals(left.mForegroundServices.get("com.test.service2"), new Integer(CONTINUING_FOREGROUND_SERVICE)); - assertEquals(left.mLaunchCount, 0); - left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service1", 350000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 350000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null); + assertEquals(left.mForegroundServices.get("com.test.service1"), null); assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 /*350000 - 100000*/); - left.update("com.test.service2", 450000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service2", 450000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 450000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"), null); - assertEquals(left.mTotalTimeForegroundServiceUsed, 250000 + 100000 /*450000 - 350000*/); + assertEquals(left.mForegroundServices.get("com.test.service2"), null); + assertEquals(left.mTotalTimeForegroundServiceUsed, + 250000 + 100000 /*450000 - 350000*/); - left.update(null, 500000, ROLLOVER_FOREGROUND_SERVICE); + left.update(null, 500000, ROLLOVER_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 450000); assertEquals(left.mTotalTimeForegroundServiceUsed, 350000); } @@ -407,76 +474,117 @@ public class UsageStatsTest { left.mPackageName = "com.test"; left.mBeginTimeStamp = 100000; - left.update("com.test.activity1", 100000, CONTINUE_PREVIOUS_DAY); - left.update("com.test.activity2", 100000, CONTINUE_PREVIOUS_DAY); + left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1); + left.update("com.test.activity2", 100000, Event.ACTIVITY_RESUMED, 2); left.update("com.test.service1", 100000, - CONTINUING_FOREGROUND_SERVICE); + CONTINUING_FOREGROUND_SERVICE, 0); left.update("com.test.service2", 100000, - CONTINUING_FOREGROUND_SERVICE); + CONTINUING_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeUsed, 100000); assertEquals(left.mLastTimeForegroundServiceUsed, 100000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"), - new Integer(CONTINUE_PREVIOUS_DAY)); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); + assertEquals(left.mActivities.get(2), Event.ACTIVITY_RESUMED); + assertEquals(left.mForegroundServices.get("com.test.service1"), new Integer(CONTINUING_FOREGROUND_SERVICE)); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"), + assertEquals(left.mForegroundServices.get("com.test.service2"), new Integer(CONTINUING_FOREGROUND_SERVICE)); - assertEquals(left.mLaunchCount, 0); + assertEquals(left.mLaunchCount, 2); - left.update("com.test.activity1", 350000, MOVE_TO_BACKGROUND); + left.update("com.test.activity1", 350000, ACTIVITY_PAUSED, 1); assertEquals(left.mLastTimeUsed, 350000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity1"), null); + assertEquals(left.mLastTimeVisible, 350000); + assertEquals(left.mActivities.get(1), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 250000 /*350000 - 100000*/); + assertEquals(left.mTotalTimeVisible, 250000 /*350000 - 100000*/); - left.update("com.test.service1", 400000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service1", 400000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 400000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service1"), null); + assertEquals(left.mForegroundServices.get("com.test.service1"), null); assertEquals(left.mTotalTimeForegroundServiceUsed, 300000 /*400000 - 100000*/); - left.update("com.test.activity2", 450000, MOVE_TO_BACKGROUND); + left.update("com.test.activity2", 450000, ACTIVITY_PAUSED, 2); assertEquals(left.mLastTimeUsed, 450000); - assertEquals(left.mLastForegroundActivityEventMap.get("com.test.activity2"), null); + assertEquals(left.mLastTimeVisible, 450000); + assertEquals(left.mActivities.get(2), ACTIVITY_PAUSED); assertEquals(left.mTotalTimeInForeground, 250000 + 100000 /*450000 - 350000*/); + assertEquals(left.mTotalTimeVisible, 250000 + 100000 /*450000 - 350000*/); - left.update("com.test.service2", 500000, FOREGROUND_SERVICE_STOP); + left.update("com.test.service2", 500000, FOREGROUND_SERVICE_STOP, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 500000); - assertEquals(left.mLastForegroundServiceEventMap.get("com.test.service2"), null); - assertEquals(left.mTotalTimeForegroundServiceUsed, 300000 + 100000 /*500000 - 400000*/); + assertEquals(left.mForegroundServices.get("com.test.service2"), null); + assertEquals(left.mTotalTimeForegroundServiceUsed, + 300000 + 100000 /*500000 - 400000*/); - left.update(null, 550000, END_OF_DAY); + left.update(null, 550000, END_OF_DAY, 0); assertEquals(left.mLastTimeUsed, 450000); + assertEquals(left.mLastTimeVisible, 550000); assertEquals(left.mTotalTimeInForeground, 350000); - left.update(null, 550000, ROLLOVER_FOREGROUND_SERVICE); + assertEquals(left.mTotalTimeVisible, 450000); + + left.update(null, 550000, ROLLOVER_FOREGROUND_SERVICE, 0); assertEquals(left.mLastTimeForegroundServiceUsed, 500000); assertEquals(left.mTotalTimeForegroundServiceUsed, 400000); } + @Test + public void testEvent_FLUSH_TO_DISK() { + testClosingEvent(FLUSH_TO_DISK); + } + + private void testClosingEvent(int eventType) { + // When these three closing events are received, all open activities/services need to be + // closed and usage stats are updated. + if (eventType != FLUSH_TO_DISK) { + fail("Closing eventType must be one of FLUSH_TO_DISK"); + } + + left.mPackageName = "com.test"; + left.mBeginTimeStamp = 100000; + + left.update("com.test.activity1", 100000, Event.ACTIVITY_RESUMED, 1); + assertEquals(left.mLastTimeUsed, 100000); + assertEquals(left.mLastTimeVisible, 100000); + assertEquals(left.mActivities.get(1), Event.ACTIVITY_RESUMED); + + left.update("com.test.service1", 150000, FOREGROUND_SERVICE_START, 0); + assertEquals(left.mLastTimeForegroundServiceUsed, 150000); + assertEquals(left.mForegroundServices.get("com.test.service1"), + new Integer(FOREGROUND_SERVICE_START)); + + left.update(null, 200000, eventType, 0); + assertEquals(left.mLastTimeUsed, 200000); + assertEquals(left.mLastTimeVisible, 200000); + assertEquals(left.mTotalTimeInForeground, 200000 - 100000); + assertEquals(left.mTotalTimeVisible, 200000 - 100000); + assertEquals(left.mLastTimeForegroundServiceUsed, 200000); + assertEquals(left.mTotalTimeForegroundServiceUsed, 200000 - 150000); + } + void compareUsageStats(UsageStats us1, UsageStats us2) { assertEquals(us1.mPackageName, us2.mPackageName); assertEquals(us1.mBeginTimeStamp, us2.mBeginTimeStamp); assertEquals(us1.mLastTimeUsed, us2.mLastTimeUsed); + assertEquals(us1.mLastTimeVisible, us2.mLastTimeVisible); assertEquals(us1.mLastTimeForegroundServiceUsed, us2.mLastTimeForegroundServiceUsed); assertEquals(us1.mTotalTimeInForeground, us2.mTotalTimeInForeground); assertEquals(us1.mTotalTimeForegroundServiceUsed, us2.mTotalTimeForegroundServiceUsed); assertEquals(us1.mAppLaunchCount, us2.mAppLaunchCount); - assertEquals(us1.mLastForegroundActivityEventMap.size(), - us2.mLastForegroundActivityEventMap.size()); - for (int i = 0; i < us1.mLastForegroundActivityEventMap.size(); i++) { - assertEquals(us1.mLastForegroundActivityEventMap.keyAt(i), - us2.mLastForegroundActivityEventMap.keyAt(i)); - assertEquals(us1.mLastForegroundActivityEventMap.valueAt(i), - us2.mLastForegroundActivityEventMap.valueAt(i)); + assertEquals(us1.mActivities.size(), + us2.mActivities.size()); + for (int i = 0; i < us1.mActivities.size(); i++) { + assertEquals(us1.mActivities.keyAt(i), + us2.mActivities.keyAt(i)); + assertEquals(us1.mActivities.valueAt(i), + us2.mActivities.valueAt(i)); } - assertEquals(us1.mLastForegroundServiceEventMap.size(), - us2.mLastForegroundServiceEventMap.size()); - for (int i = 0; i < us1.mLastForegroundServiceEventMap.size(); i++) { - assertEquals(us1.mLastForegroundServiceEventMap.keyAt(i), - us2.mLastForegroundServiceEventMap.keyAt(i)); - assertEquals(us1.mLastForegroundServiceEventMap.valueAt(i), - us2.mLastForegroundServiceEventMap.valueAt(i)); + assertEquals(us1.mForegroundServices.size(), + us2.mForegroundServices.size()); + for (int i = 0; i < us1.mForegroundServices.size(); i++) { + assertEquals(us1.mForegroundServices.keyAt(i), + us2.mForegroundServices.keyAt(i)); + assertEquals(us1.mForegroundServices.valueAt(i), + us2.mForegroundServices.valueAt(i)); } assertEquals(us1.mChooserCounts, us2.mChooserCounts); } diff --git a/core/tests/coretests/src/android/os/BinderWorkSourceService.java b/core/tests/coretests/src/android/os/BinderWorkSourceService.java index ac8d7ab98344..3bca5fbab486 100644 --- a/core/tests/coretests/src/android/os/BinderWorkSourceService.java +++ b/core/tests/coretests/src/android/os/BinderWorkSourceService.java @@ -25,9 +25,25 @@ import android.content.Intent; public class BinderWorkSourceService extends Service { private final IBinderWorkSourceService.Stub mBinder = new IBinderWorkSourceService.Stub() { + public int getBinderCallingUid() { + return Binder.getCallingUid(); + } + public int getIncomingWorkSourceUid() { return Binder.getCallingWorkSourceUid(); } + + public int getThreadLocalWorkSourceUid() { + return ThreadLocalWorkSource.getUid(); + } + + public void setWorkSourceProvider(int uid) { + Binder.setWorkSourceProvider(() -> uid); + } + + public void clearWorkSourceProvider() { + Binder.setWorkSourceProvider(Binder::getCallingUid); + } }; public IBinder onBind(Intent intent) { diff --git a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java index ec178031cc45..d1dbd3ccba33 100644 --- a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java +++ b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java @@ -163,4 +163,24 @@ public class BinderWorkSourceTest { // Initial work source restored. assertEquals(UID, Binder.getCallingWorkSourceUid()); } + + @Test + public void workSourceProvider_default() throws Exception { + Binder.clearCallingWorkSource(); + mService.clearWorkSourceProvider(); + assertEquals(Process.myUid(), mService.getThreadLocalWorkSourceUid()); + } + + @Test + public void workSourceProvider_customProvider() throws Exception { + Binder.clearCallingWorkSource(); + mService.clearWorkSourceProvider(); + // Calling uid should not be used. + mService.setWorkSourceProvider(SECOND_UID); + try { + assertEquals(SECOND_UID, mService.getThreadLocalWorkSourceUid()); + } finally { + mService.clearWorkSourceProvider(); + } + } } diff --git a/core/tests/coretests/src/android/os/IBinderWorkSourceService.aidl b/core/tests/coretests/src/android/os/IBinderWorkSourceService.aidl index 05d4e829be0a..93224003e9a5 100644 --- a/core/tests/coretests/src/android/os/IBinderWorkSourceService.aidl +++ b/core/tests/coretests/src/android/os/IBinderWorkSourceService.aidl @@ -18,4 +18,8 @@ package android.os; interface IBinderWorkSourceService { int getIncomingWorkSourceUid(); + int getBinderCallingUid(); + int getThreadLocalWorkSourceUid(); + void setWorkSourceProvider(int uid); + void clearWorkSourceProvider(); } diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java index da17b56bc0fa..a828f4418515 100644 --- a/core/tests/coretests/src/android/os/PowerManagerTest.java +++ b/core/tests/coretests/src/android/os/PowerManagerTest.java @@ -16,13 +16,32 @@ package android.os; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.uiautomator.UiDevice; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; +import org.junit.After; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + public class PowerManagerTest extends AndroidTestCase { private PowerManager mPm; + private UiDevice mUiDevice; + private Executor mExec = Executors.newSingleThreadExecutor(); + @Mock + private PowerManager.ThermalStatusCallback mCallback; + private static final long CALLBACK_TIMEOUT_MILLI_SEC = 5000; /** * Setup any common data for the upcoming tests. @@ -30,7 +49,18 @@ public class PowerManagerTest extends AndroidTestCase { @Override public void setUp() throws Exception { super.setUp(); + MockitoAnnotations.initMocks(this); + mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); mPm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mUiDevice.executeShellCommand("cmd thermalservice override-status 0"); + } + + /** + * Reset data for the upcoming tests. + */ + @After + public void tearDown() throws Exception { + mUiDevice.executeShellCommand("cmd thermalservice reset"); } /** @@ -137,4 +167,35 @@ public class PowerManagerTest extends AndroidTestCase { // TODO: Threaded test (needs handler) to make sure timed wakelocks work too } + + @Test + public void testGetThermalStatus() throws Exception { + int status = 0; + assertEquals(status, mPm.getCurrentThermalStatus()); + status = 3; + mUiDevice.executeShellCommand("cmd thermalservice override-status " + + Integer.toString(status)); + assertEquals(status, mPm.getCurrentThermalStatus()); + } + + @Test + public void testThermalStatusCallback() throws Exception { + mPm.registerThermalStatusCallback(mCallback, mExec); + verify(mCallback, timeout(CALLBACK_TIMEOUT_MILLI_SEC) + .times(1)).onStatusChange(0); + reset(mCallback); + int status = 3; + mUiDevice.executeShellCommand("cmd thermalservice override-status " + + Integer.toString(status)); + verify(mCallback, timeout(CALLBACK_TIMEOUT_MILLI_SEC) + .times(1)).onStatusChange(status); + reset(mCallback); + mPm.unregisterThermalStatusCallback(mCallback); + status = 2; + mUiDevice.executeShellCommand("cmd thermalservice override-status " + + Integer.toString(status)); + verify(mCallback, timeout(CALLBACK_TIMEOUT_MILLI_SEC) + .times(0)).onStatusChange(status); + + } } diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index ed9c3d5b2b70..3a37fb627b26 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -183,6 +183,7 @@ public class SettingsBackupTest { Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE, Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS, Settings.Global.CONTACT_METADATA_SYNC_ENABLED, + Settings.Global.CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED, Settings.Global.CONVERSATION_ACTIONS_UPDATE_CONTENT_URL, Settings.Global.CONVERSATION_ACTIONS_UPDATE_METADATA_URL, Settings.Global.CONTACTS_DATABASE_WAL_ENABLED, @@ -247,6 +248,7 @@ public class SettingsBackupTest { Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED, Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, Settings.Global.ENHANCED_4G_MODE_ENABLED, Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES, Settings.Global.ERROR_LOGCAT_PREFIX, @@ -413,9 +415,9 @@ public class SettingsBackupTest { Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG, Settings.Global.SHOW_TEMPERATURE_WARNING, + Settings.Global.SIGNED_CONFIG_VERSION, Settings.Global.SMART_SELECTION_UPDATE_CONTENT_URL, Settings.Global.SMART_SELECTION_UPDATE_METADATA_URL, - Settings.Global.SMART_SUGGESTIONS_SERVICE_EXPLICITLY_ENABLED, Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, Settings.Global.SMS_OUTGOING_CHECK_INTERVAL_MS, Settings.Global.SMS_OUTGOING_CHECK_MAX_COUNT, @@ -469,7 +471,9 @@ public class SettingsBackupTest { Settings.Global.GPU_DEBUG_APP, Settings.Global.GPU_DEBUG_LAYERS, Settings.Global.GPU_DEBUG_LAYERS_GLES, - Settings.Global.ANGLE_ENABLED_APP, + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE, + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, Settings.Global.UPDATED_GFX_DRIVER_DEV_OPT_IN_APP, Settings.Global.GPU_DEBUG_LAYER_APP, Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, @@ -538,7 +542,8 @@ public class SettingsBackupTest { Settings.Global.OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION, Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS, - Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS); + Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS, + Settings.Global.BACKUP_MULTI_USER_ENABLED); private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS = newHashSet( Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java index 108585dc0886..04e880225b96 100644 --- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java +++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java @@ -16,6 +16,10 @@ package android.provider; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.junit.Assert.assertThat; + import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -26,6 +30,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.database.Cursor; import android.net.Uri; +import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.test.AndroidTestCase; @@ -33,10 +38,19 @@ import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.Suppress; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** Unit test for SettingsProvider. */ public class SettingsProviderTest extends AndroidTestCase { + /** + * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System + * API. + */ + private static final Uri CONFIG_CONTENT_URI = + Uri.parse("content://" + Settings.AUTHORITY + "/config"); + @MediumTest public void testNameValueCache() { ContentResolver r = getContext().getContentResolver(); @@ -379,4 +393,109 @@ public class SettingsProviderTest extends AndroidTestCase { assertTrue(ssaid.equals(ssaid2)); } + + @MediumTest + public void testCall_putAndGetConfig() { + ContentResolver r = getContext().getContentResolver(); + String name = "key1"; + String value = "value1"; + String newValue = "value2"; + Bundle args = new Bundle(); + args.putString(Settings.NameValueTable.VALUE, value); + + try { + // value is empty + Bundle results = + r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null); + assertNull(results.get(Settings.NameValueTable.VALUE)); + + // save value + results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args); + assertNull(results); + + // value is no longer empty + results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null); + assertEquals(value, results.get(Settings.NameValueTable.VALUE)); + + // save new value + args.putString(Settings.NameValueTable.VALUE, newValue); + r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args); + + // new value is returned + results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null); + assertEquals(newValue, results.get(Settings.NameValueTable.VALUE)); + } finally { + // clean up + r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null); + } + } + + @MediumTest + public void testCall_deleteConfig() { + ContentResolver r = getContext().getContentResolver(); + String name = "key1"; + String value = "value1"; + Bundle args = new Bundle(); + args.putString(Settings.NameValueTable.VALUE, value); + + try { + // save value + r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args); + + // get value + Bundle results = + r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null); + assertEquals(value, results.get(Settings.NameValueTable.VALUE)); + + // delete value + results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null); + + // value is empty now + results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null); + assertNull(results.get(Settings.NameValueTable.VALUE)); + } finally { + // clean up + r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null); + } + } + + @MediumTest + public void testCall_listConfig() { + ContentResolver r = getContext().getContentResolver(); + String prefix = "foo"; + String newPrefix = "bar"; + String name = prefix + "/" + "key1"; + String newName = newPrefix + "/" + "key1"; + String value = "value1"; + String newValue = "value2"; + Bundle args = new Bundle(); + args.putString(Settings.NameValueTable.VALUE, value); + + try { + // save both values + r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args); + args.putString(Settings.NameValueTable.VALUE, newValue); + r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args); + + // list all values + Bundle result = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, + null, null); + Map<String, String> keyValueMap = + (HashMap) result.getSerializable(Settings.NameValueTable.VALUE); + assertThat(keyValueMap.size(), greaterThanOrEqualTo(2)); + assertEquals(value, keyValueMap.get(name)); + assertEquals(newValue, keyValueMap.get(newName)); + + // list values for prefix + args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix); + result = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args); + keyValueMap = (HashMap) result.getSerializable(Settings.NameValueTable.VALUE); + assertThat(keyValueMap, aMapWithSize(1)); + assertEquals(value, keyValueMap.get(name)); + } finally { + // clean up + r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null); + r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null); + } + } } diff --git a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java index 8d6fbd563d6c..61ab1526abc4 100644 --- a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java +++ b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java @@ -28,7 +28,8 @@ import com.google.common.collect.Lists; import org.junit.Test; import org.junit.runner.RunWith; -// TODO(b/73862682): Add tests for RecoveryCertPath +import java.security.cert.CertPath; + @RunWith(AndroidJUnit4.class) @SmallTest public class KeyChainSnapshotTest { @@ -43,35 +44,41 @@ public class KeyChainSnapshotTest { private static final int USER_SECRET_TYPE = KeyChainProtectionParams.TYPE_LOCKSCREEN; private static final String KEY_ALIAS = "steph"; private static final byte[] KEY_MATERIAL = new byte[] { 3, 5, 7, 9, 1 }; + private static final CertPath CERT_PATH = TestData.getThmCertPath(); @Test - public void build_setsCounterId() { + public void build_setsCounterId() throws Exception { assertEquals(COUNTER_ID, createKeyChainSnapshot().getCounterId()); } @Test - public void build_setsSnapshotVersion() { + public void build_setsSnapshotVersion() throws Exception { assertEquals(SNAPSHOT_VERSION, createKeyChainSnapshot().getSnapshotVersion()); } @Test - public void build_setsMaxAttempts() { + public void build_setsMaxAttempts() throws Exception { assertEquals(MAX_ATTEMPTS, createKeyChainSnapshot().getMaxAttempts()); } @Test - public void build_setsServerParams() { + public void build_setsServerParams() throws Exception { assertArrayEquals(SERVER_PARAMS, createKeyChainSnapshot().getServerParams()); } @Test - public void build_setsRecoveryKeyBlob() { + public void build_setsRecoveryKeyBlob() throws Exception { assertArrayEquals(RECOVERY_KEY_BLOB, createKeyChainSnapshot().getEncryptedRecoveryKeyBlob()); } @Test - public void build_setsKeyChainProtectionParams() { + public void build_setsCertPath() throws Exception { + assertEquals(CERT_PATH, createKeyChainSnapshot().getTrustedHardwareCertPath()); + } + + @Test + public void build_setsKeyChainProtectionParams() throws Exception { KeyChainSnapshot snapshot = createKeyChainSnapshot(); assertEquals(1, snapshot.getKeyChainProtectionParams().size()); @@ -85,7 +92,7 @@ public class KeyChainSnapshotTest { } @Test - public void build_setsWrappedApplicationKeys() { + public void build_setsWrappedApplicationKeys() throws Exception { KeyChainSnapshot snapshot = createKeyChainSnapshot(); assertEquals(1, snapshot.getWrappedApplicationKeys().size()); @@ -95,42 +102,49 @@ public class KeyChainSnapshotTest { } @Test - public void writeToParcel_writesCounterId() { + public void writeToParcel_writesCounterId() throws Exception { KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot()); assertEquals(COUNTER_ID, snapshot.getCounterId()); } @Test - public void writeToParcel_writesSnapshotVersion() { + public void writeToParcel_writesSnapshotVersion() throws Exception { KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot()); assertEquals(SNAPSHOT_VERSION, snapshot.getSnapshotVersion()); } @Test - public void writeToParcel_writesMaxAttempts() { + public void writeToParcel_writesMaxAttempts() throws Exception { KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot()); assertEquals(MAX_ATTEMPTS, snapshot.getMaxAttempts()); } @Test - public void writeToParcel_writesServerParams() { + public void writeToParcel_writesServerParams() throws Exception { KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot()); assertArrayEquals(SERVER_PARAMS, snapshot.getServerParams()); } @Test - public void writeToParcel_writesKeyRecoveryBlob() { + public void writeToParcel_writesKeyRecoveryBlob() throws Exception { KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot()); assertArrayEquals(RECOVERY_KEY_BLOB, snapshot.getEncryptedRecoveryKeyBlob()); } @Test - public void writeToParcel_writesKeyChainProtectionParams() { + public void writeToParcel_writesCertPath() throws Exception { + KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot()); + + assertEquals(CERT_PATH, snapshot.getTrustedHardwareCertPath()); + } + + @Test + public void writeToParcel_writesKeyChainProtectionParams() throws Exception { KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot()); assertEquals(1, snapshot.getKeyChainProtectionParams().size()); @@ -144,7 +158,7 @@ public class KeyChainSnapshotTest { } @Test - public void writeToParcel_writesWrappedApplicationKeys() { + public void writeToParcel_writesWrappedApplicationKeys() throws Exception { KeyChainSnapshot snapshot = writeToThenReadFromParcel(createKeyChainSnapshot()); assertEquals(1, snapshot.getWrappedApplicationKeys().size()); @@ -153,7 +167,7 @@ public class KeyChainSnapshotTest { assertArrayEquals(KEY_MATERIAL, wrappedApplicationKey.getEncryptedKeyMaterial()); } - private static KeyChainSnapshot createKeyChainSnapshot() { + private static KeyChainSnapshot createKeyChainSnapshot() throws Exception { return new KeyChainSnapshot.Builder() .setCounterId(COUNTER_ID) .setSnapshotVersion(SNAPSHOT_VERSION) @@ -162,6 +176,7 @@ public class KeyChainSnapshotTest { .setEncryptedRecoveryKeyBlob(RECOVERY_KEY_BLOB) .setKeyChainProtectionParams(Lists.newArrayList(createKeyChainProtectionParams())) .setWrappedApplicationKeys(Lists.newArrayList(createWrappedApplicationKey())) + .setTrustedHardwareCertPath(CERT_PATH) .build(); } diff --git a/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java b/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java new file mode 100644 index 000000000000..dd8cd8dbd0c6 --- /dev/null +++ b/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018 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.security.keystore.recovery; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.os.Parcel; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.security.cert.CertificateException; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RecoveryCertPathTest { + + @Test + public void createRecoveryCertPath_getCertPath_succeeds() throws Exception { + RecoveryCertPath recoveryCertPath = RecoveryCertPath.createRecoveryCertPath( + TestData.getThmCertPath()); + assertEquals(TestData.getThmCertPath(), recoveryCertPath.getCertPath()); + } + + @Test + public void getCertPath_throwsIfCannnotDecode() { + Parcel parcel = Parcel.obtain(); + parcel.writeByteArray(new byte[]{0, 1, 2, 3}); + parcel.setDataPosition(0); + RecoveryCertPath recoveryCertPath = RecoveryCertPath.CREATOR.createFromParcel(parcel); + parcel.recycle(); + + try { + recoveryCertPath.getCertPath(); + fail("Did not throw when attempting to decode invalid cert path"); + } catch (CertificateException e) { + // Expected + } + } + + @Test + public void writeToParcel_writesCertPath() throws Exception { + RecoveryCertPath recoveryCertPath = + writeToThenReadFromParcel( + RecoveryCertPath.createRecoveryCertPath(TestData.getThmCertPath())); + assertEquals(TestData.getThmCertPath(), recoveryCertPath.getCertPath()); + } + + private RecoveryCertPath writeToThenReadFromParcel(RecoveryCertPath recoveryCertPath) { + Parcel parcel = Parcel.obtain(); + recoveryCertPath.writeToParcel(parcel, /*flags=*/ 0); + parcel.setDataPosition(0); + RecoveryCertPath fromParcel = RecoveryCertPath.CREATOR.createFromParcel(parcel); + parcel.recycle(); + return fromParcel; + } +} diff --git a/core/tests/coretests/src/android/security/keystore/recovery/TestData.java b/core/tests/coretests/src/android/security/keystore/recovery/TestData.java new file mode 100644 index 000000000000..829a92a806b9 --- /dev/null +++ b/core/tests/coretests/src/android/security/keystore/recovery/TestData.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018 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.security.keystore.recovery; + +import java.io.ByteArrayInputStream; +import java.security.cert.CertPath; +import java.security.cert.CertificateFactory; +import java.util.Base64; + +/** This class provides data for testing purposes. */ +class TestData { + + private static final String THM_CERT_PATH_BASE64 = "" + + "MIIIXTCCBRowggMCoAMCAQICEB35ZwzVpI9ssXg9SAehnU0wDQYJKoZIhvcNAQEL" + + "BQAwMTEvMC0GA1UEAxMmR29vZ2xlIENsb3VkIEtleSBWYXVsdCBTZXJ2aWNlIFJv" + + "b3QgQ0EwHhcNMTgwNTA3MTg1ODEwWhcNMjgwNTA4MTg1ODEwWjA5MTcwNQYDVQQD" + + "Ey5Hb29nbGUgQ2xvdWQgS2V5IFZhdWx0IFNlcnZpY2UgSW50ZXJtZWRpYXRlIENB" + + "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA73TrvH3j6zEimpcc32tx" + + "2iupWwfyzdE5l4Ejc5EBYzx0aZH6b/KDuutwustk0IoyjlGySMBz/21YgWejIm+n" + + "duAlpk7WY5kYHp0XWtzdmxZknmWTqugPeNZeiKEjoDmpyIbY6N+f13hQ2RVh+WDT" + + "EowQ/i04WBL75chshlIG+3A42g5Qr7DZEKdT9oJQqkntzj0cGyJ5X8BwjeTiJrvY" + + "k2Kn/0555/Kpp65G3Rf29VPPU3i67kthAT3SavLBpH03S4WZ+QlfrAiGQziydtz9" + + "t7mSk1xefjax5ZWAuJAfCbKfI3VWAcaUr4P57BzmDcSi0jgs1aM3t2BrPfAMRxWv" + + "35yDZnrC+HipzkjyDGBfHmFgoglyhc9e/Kj3mSusO0Rq1wguVXKs2hKXRoaGJuHt" + + "e3YIwTC1pLznqvolhD1nPoXf8rMzgHRzlc9H8iXsgB1p7975nh5WCPrMDX2eAmYd" + + "a0xTMccTeBzIM2ohxQsxlh5rsjXVNU3ihbWkHquzIiwFcAtldP3dMksj0dn/DnYD" + + "yokjEgU/z2I216E93x9hmKkEk6Pp7o8t/z6lwMT9FJIuzp7NREnWCSi+e5s2E7FD" + + "j6S7xY2zEIUHrmwuuJc0jzJnwdZ+0myinaTmBDvBXR5cU1cmEAZoheCAoRv9Z/6o" + + "ASczLF0C4uuVfA5GXcAm14cCAwEAAaMmMCQwDgYDVR0PAQH/BAQDAgGGMBIGA1Ud" + + "EwEB/wQIMAYBAf8CAQEwDQYJKoZIhvcNAQELBQADggIBAEPht79yQm8woQbPB1Bs" + + "eotkzJtTWTO9fnIWwNiRfQ3vJFXf69ghE77wUS13Ez3FlgNPj0Qxmg5ouE0d2yYV" + + "4AUrXnEGZELcyN2XHRXyNK0zXgnr3x6eZyY7QfgGKJgkyja5TS6ZPWGyaLKhClS0" + + "AYZSzWJtz0+AkGCdTbmyy7ShdXJ+GfnmssbndZA62VhcjeQmHsDq7V3PKAsp4/B9" + + "PzcnTrgkUFNnP1F1pr7JpUUX3xyRFy6gjIrUx1fcOFRxFYPWGLLMZ6P41rafm+M/" + + "CbBNr5CY7NrZjr34jLqWycfYes49o9OK44X/wPrxj0Sjg+VrW21+AJ9vrM7DS5hE" + + "QX1lDbDtQGkk3N1vgCTo6xt9LXsEu4xUT5bk7YAfpJqM0ltDFPwYAGCbjSkVT/M5" + + "JVZkKiUW668Us67x8yZc/5bxbvTA+5xrYhak/VYIBY6qub4J+bKwadw6uBgxnq4P" + + "hwgwjfaoJy9YAXCswjCtaE9GwkVmRnJE9vFjJ33IGf37hFTYEHBFy4FomVmQwRFZ" + + "TIe7tkKDq9i18F7lzBPJPO6wEG8bxi4csatrjcVHR9erpY5u6ebtkKG8qsan9qzh" + + "iWAgSytiT++HejZeoQ+RRgQWjupjdDo5/0oSdQqvaN8Ah6C2J+ecCZ12Lu0FwF+t" + + "t9Ie3pF6W8TzxzuMdFWq+afvMIIDOzCCASOgAwIBAgIRAOTj/iNQb6/Qit7zAW9n" + + "cL0wDQYJKoZIhvcNAQELBQAwOTE3MDUGA1UEAxMuR29vZ2xlIENsb3VkIEtleSBW" + + "YXVsdCBTZXJ2aWNlIEludGVybWVkaWF0ZSBDQTAeFw0xODA1MDcyMjE4MTFaFw0y" + + "MzA1MDgyMjE4MTFaMDIxMDAuBgNVBAMTJ0dvb2dsZSBDbG91ZCBLZXkgVmF1bHQg" + + "U2VydmljZSBFbmRwb2ludDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABI4MEUp5" + + "IHwATNfpBuJYIUX6JMsHZt798YO0JlWYy6nVVa1lxf9c+xxONJh+T5aio370RlIE" + + "uiq5R7vCHt0VGsCjEDAOMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB" + + "AGf6QU58lU+gGzy8hnp0suR/ixC++CExtf39pDHkdfU/e3ui4ROR+pjQ5F7okDFW" + + "eKSCNcJZ7tyXMJ9g7/I0qVY8Bj/gRnlVokdl/wD5PiL9GIzqWfnHNe3T+xrAAAgO" + + "D0bEmjgwNYmekfUIYQczd04d7ZMGnmAkpVH/0O2mf9q5x9fMlbKuAygUqQ/gmnlg" + + "xKfl9DSRWi4oMBOqlKlCRP1XAh3anu92+M/EhsFbyc07CWZY0SByX5M/cHVMLhUX" + + "jZHvcYLyOmJWJmXznidgyNeIR6t9yDB55iCt7WSn3qMY+9vA9ELzt8jYpBNaKc0G" + + "bWQkRzYWegkf4kMis98eQ3SnAKbRz6669nmuAdxKs9/LK6BlFOFw1xvsTRQ96dBa" + + "oiX2XGhou+Im0Td/AMs0Aigz2N+Ujq/yW//35GZQfdGGIYtFbkcltStygjIJyAM1" + + "pBhyBBkJhOhRpO4fXh98aq8H5J7R9i5A9WpnDstAxPxcNCDWn0O/WxhPvVZkFTpi" + + "NXh9dnlJ/kZe+j+z5ZMaxW435drLPx2AQKjXA9GgGrFPltTUyGycmEGtuxLvSnm/" + + "zPlmk5FUk7x2wEr0+bZ3cx0JHHgAtgXpe0jkDi8Bw8O3X7mUOjxVhYU6auiYJezW" + + "9LGmweaKwYvS04UCWOReolUVexob9LI/VX1JrrwD3s7k"; + + static CertPath getThmCertPath() { + try { + return decodeCertPath(THM_CERT_PATH_BASE64); + } catch (Exception e) { + // Should never happen + throw new RuntimeException(e); + } + } + + private static CertPath decodeCertPath(String base64CertPath) throws Exception { + byte[] certPathBytes = Base64.getDecoder().decode(base64CertPath); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return certFactory.generateCertPath(new ByteArrayInputStream(certPathBytes), "PkiPath"); + } +} diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java index 3d15eb9577b5..a0dca2c0dc4f 100644 --- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java +++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java @@ -133,7 +133,8 @@ public class MeasuredParagraphTest { public void buildForStaticLayout() { MeasuredParagraph mt = null; - mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, false, false, null); + mt = MeasuredParagraph.buildForStaticLayout( + PAINT, "XXX", 0, 3, LTR, false, false, null /* no hint */, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -147,8 +148,8 @@ public class MeasuredParagraphTest { assertNotNull(mt.getMeasuredText()); // Recycle it - MeasuredParagraph mt2 = - MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, false, false, mt); + MeasuredParagraph mt2 = MeasuredParagraph.buildForStaticLayout( + PAINT, "_VVV_", 1, 4, RTL, false, false, null /* no hint */, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index ed80cd7e375c..2ad6028960df 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -19,6 +19,7 @@ package android.view; import static android.view.InsetsState.TYPE_TOP_BAR; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; +import static org.mockito.Mockito.mock; import android.platform.test.annotations.Presubmit; import android.support.test.filters.FlakyTest; @@ -33,7 +34,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class InsetsControllerTest { - private InsetsController mController = new InsetsController(); + private InsetsController mController = new InsetsController(mock(ViewRootImpl.class)); private SurfaceSession mSession = new SurfaceSession(); private SurfaceControl mLeash; diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index 5a20ba2522b1..6d0f98433168 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -53,7 +53,7 @@ public class InsetsSourceConsumerTest { .setName("testSurface") .build(); mConsumer = new InsetsSourceConsumer(TYPE_TOP_BAR, new InsetsState(), - () -> mMockTransaction); + () -> mMockTransaction, mMockController); mConsumer.setControl(new InsetsSourceControl(TYPE_TOP_BAR, mLeash)); } diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java index f0faaf6153b1..4a6c093e3bd1 100644 --- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java @@ -16,6 +16,9 @@ package android.view.textclassifier; +import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL; +import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE; + import static com.google.common.truth.Truth.assertThat; import android.app.Person; @@ -27,16 +30,26 @@ import com.google.android.textclassifier.ActionsSuggestionsModel; import org.junit.Test; import org.junit.runner.RunWith; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collections; +import java.util.Locale; +import java.util.function.Function; @SmallTest @RunWith(AndroidJUnit4.class) public class ActionsSuggestionsHelperTest { + private static final String LOCALE_TAG = Locale.US.toLanguageTag(); + private static final Function<CharSequence, String> LANGUAGE_DETECTOR = + charSequence -> LOCALE_TAG; + @Test public void testToNativeMessages_emptyInput() { ActionsSuggestionsModel.ConversationMessage[] conversationMessages = - ActionsSuggestionsHelper.toNativeMessages(Collections.emptyList()); + ActionsSuggestionsHelper.toNativeMessages( + Collections.emptyList(), LANGUAGE_DETECTOR); assertThat(conversationMessages).isEmpty(); } @@ -44,114 +57,89 @@ public class ActionsSuggestionsHelperTest { @Test public void testToNativeMessages_noTextMessages() { ConversationActions.Message messageWithoutText = - new ConversationActions.Message.Builder().build(); + new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( - Collections.singletonList(messageWithoutText)); + Collections.singletonList(messageWithoutText), LANGUAGE_DETECTOR); assertThat(conversationMessages).isEmpty(); } @Test - public void testToNativeMessages_missingPersonInFirstMessage() { - ConversationActions.Message firstMessage = - new ConversationActions.Message.Builder() - .setText("first") - .build(); - ConversationActions.Message secondMessage = - new ConversationActions.Message.Builder() - .setText("second") - .setAuthor(new Person.Builder().build()) - .build(); - ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder() - .setText("third") - .setAuthor(ConversationActions.Message.PERSON_USER_LOCAL) - .build(); - - ActionsSuggestionsModel.ConversationMessage[] conversationMessages = - ActionsSuggestionsHelper.toNativeMessages( - Arrays.asList(firstMessage, secondMessage, thirdMessage)); - - assertThat(conversationMessages).hasLength(2); - assertNativeMessage(conversationMessages[0], secondMessage.getText(), 1); - assertNativeMessage(conversationMessages[1], thirdMessage.getText(), 0); - } + public void testToNativeMessages_userIdEncoding() { + Person userA = new Person.Builder().setName("userA").build(); + Person userB = new Person.Builder().setName("userB").build(); - @Test - public void testToNativeMessages_missingPersonInMiddleOfConversation() { ConversationActions.Message firstMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(userB) .setText("first") - .setAuthor(new Person.Builder().setName("first").build()) .build(); ConversationActions.Message secondMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(userA) .setText("second") .build(); ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(PERSON_USER_LOCAL) .setText("third") - .setAuthor(new Person.Builder().setName("third").build()) .build(); ConversationActions.Message fourthMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(userA) .setText("fourth") - .setAuthor(new Person.Builder().setName("fourth").build()) .build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( - Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage)); + Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage), + LANGUAGE_DETECTOR); - assertThat(conversationMessages).hasLength(2); - assertNativeMessage(conversationMessages[0], thirdMessage.getText(), 2); - assertNativeMessage(conversationMessages[1], fourthMessage.getText(), 1); + assertThat(conversationMessages).hasLength(4); + assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2, 0); + assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); + assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0, 0); + assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1, 0); } @Test - public void testToNativeMessages_userIdEncoding() { - Person userA = new Person.Builder().setName("userA").build(); - Person userB = new Person.Builder().setName("userB").build(); - + public void testToNativeMessages_referenceTime() { ConversationActions.Message firstMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(PERSON_USER_REMOTE) .setText("first") - .setAuthor(userB) + .setReferenceTime(createZonedDateTimeFromMsUtc(1000)) .build(); ConversationActions.Message secondMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(PERSON_USER_REMOTE) .setText("second") - .setAuthor(userA) .build(); ConversationActions.Message thirdMessage = - new ConversationActions.Message.Builder() + new ConversationActions.Message.Builder(PERSON_USER_REMOTE) .setText("third") - .setAuthor(ConversationActions.Message.PERSON_USER_LOCAL) - .build(); - ConversationActions.Message fourthMessage = - new ConversationActions.Message.Builder() - .setText("fourth") - .setAuthor(userA) + .setReferenceTime(createZonedDateTimeFromMsUtc(2000)) .build(); ActionsSuggestionsModel.ConversationMessage[] conversationMessages = ActionsSuggestionsHelper.toNativeMessages( - Arrays.asList(firstMessage, secondMessage, thirdMessage, fourthMessage)); + Arrays.asList(firstMessage, secondMessage, thirdMessage), + LANGUAGE_DETECTOR); - assertThat(conversationMessages).hasLength(4); - assertNativeMessage(conversationMessages[0], firstMessage.getText(), 2); - assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1); - assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 0); - assertNativeMessage(conversationMessages[3], fourthMessage.getText(), 1); + assertThat(conversationMessages).hasLength(3); + assertNativeMessage(conversationMessages[0], firstMessage.getText(), 1, 1000); + assertNativeMessage(conversationMessages[1], secondMessage.getText(), 1, 0); + assertNativeMessage(conversationMessages[2], thirdMessage.getText(), 1, 2000); + } + + private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneId.of("UTC")); } private static void assertNativeMessage( ActionsSuggestionsModel.ConversationMessage nativeMessage, CharSequence text, - int userId) { + int userId, + long referenceTimeInMsUtc) { assertThat(nativeMessage.getText()).isEqualTo(text.toString()); assertThat(nativeMessage.getUserId()).isEqualTo(userId); + assertThat(nativeMessage.getLocales()).isEqualTo(LOCALE_TAG); + assertThat(nativeMessage.getReferenceTimeMsUtc()).isEqualTo(referenceTimeInMsUtc); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java index 91a54409608a..aaf7312fe0e6 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java @@ -189,6 +189,7 @@ public class TextClassificationTest { Instant.ofEpochMilli(946771200000L), // 2000-01-02 ZoneId.of("UTC")); final String text = "text"; + final String packageName = "packageName"; final TextClassification.Request reference = new TextClassification.Request.Builder(text, 0, text.length()) @@ -196,6 +197,7 @@ public class TextClassificationTest { .setReferenceTime(referenceTime) .setExtras(BUNDLE) .build(); + reference.setCallingPackageName(packageName); // Parcel and unparcel. final Parcel parcel = Parcel.obtain(); @@ -204,12 +206,13 @@ public class TextClassificationTest { final TextClassification.Request result = TextClassification.Request.CREATOR.createFromParcel(parcel); - assertEquals(text, result.getText()); + assertEquals(text, result.getText().toString()); assertEquals(0, result.getStartIndex()); assertEquals(text.length(), result.getEndIndex()); assertEquals(referenceTime, result.getReferenceTime()); assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags()); assertEquals(referenceTime, result.getReferenceTime()); assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY)); + assertEquals(packageName, result.getCallingPackageName()); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java index aec4571252e7..9b5c0347bdb6 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java @@ -373,7 +373,10 @@ public class TextClassifierTest { public void testSuggestConversationActions_textReplyOnly_maxThree() { if (isTextClassifierDisabled()) return; ConversationActions.Message message = - new ConversationActions.Message.Builder().setText("Where are you?").build(); + new ConversationActions.Message.Builder( + ConversationActions.Message.PERSON_USER_REMOTE) + .setText("Hello") + .build(); ConversationActions.TypeConfig typeConfig = new ConversationActions.TypeConfig.Builder().includeTypesFromTextClassifier(false) .setIncludedTypes( diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java index 75ca769294ce..1dcaed6f6a1e 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java @@ -69,10 +69,12 @@ public final class TextLanguageTest { final String bundleKey = "experiment.str"; final Bundle bundle = new Bundle(); bundle.putString(bundleKey, "bundle"); + final String packageName = "packageName"; final TextLanguage.Request reference = new TextLanguage.Request.Builder(text) .setExtras(bundle) .build(); + reference.setCallingPackageName(packageName); final Parcel parcel = Parcel.obtain(); reference.writeToParcel(parcel, 0); @@ -81,5 +83,6 @@ public final class TextLanguageTest { assertEquals(text, result.getText()); assertEquals("bundle", result.getExtras().getString(bundleKey)); + assertEquals(packageName, result.getCallingPackageName()); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java index f6ec0e6480f2..f022d040246b 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java @@ -64,7 +64,7 @@ public class TextLinksTest { public void testParcel() { final String fullText = "this is just a test"; final TextLinks reference = new TextLinks.Builder(fullText) - .addLink(0, 4, getEntityScores(0.f, 0.f, 1.f)) + .addLink(0, 4, getEntityScores(0.f, 0.f, 1.f), BUNDLE) .addLink(5, 12, getEntityScores(.8f, .1f, .5f)) .setExtras(BUNDLE) .build(); @@ -82,6 +82,7 @@ public class TextLinksTest { assertEquals(1, resultList.get(0).getEntityCount()); assertEquals(TextClassifier.TYPE_OTHER, resultList.get(0).getEntity(0)); assertEquals(1.f, resultList.get(0).getConfidenceScore(TextClassifier.TYPE_OTHER), 1e-7f); + assertEquals(BUNDLE_VALUE, resultList.get(0).getExtras().getString(BUNDLE_KEY)); assertEquals(5, resultList.get(1).getStart()); assertEquals(12, resultList.get(1).getEnd()); assertEquals(3, resultList.get(1).getEntityCount()); @@ -96,6 +97,7 @@ public class TextLinksTest { @Test public void testParcelOptions() { + final String packageName = "packageName"; final TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.create( Arrays.asList(TextClassifier.HINT_TEXT_IS_EDITABLE), Arrays.asList("a", "b", "c"), @@ -105,6 +107,7 @@ public class TextLinksTest { .setEntityConfig(entityConfig) .setExtras(BUNDLE) .build(); + reference.setCallingPackageName(packageName); // Parcel and unparcel. final Parcel parcel = Parcel.obtain(); @@ -119,5 +122,6 @@ public class TextLinksTest { assertEquals(new HashSet<String>(Arrays.asList("a", "c")), result.getEntityConfig().resolveEntityListModifications(Collections.emptyList())); assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY)); + assertEquals(packageName, result.getCallingPackageName()); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java index 7ea5108bc3bb..2ea49f7d21be 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java @@ -75,11 +75,13 @@ public class TextSelectionTest { @Test public void testParcelRequest() { final String text = "text"; + final String packageName = "packageName"; final TextSelection.Request reference = new TextSelection.Request.Builder(text, 0, text.length()) .setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY)) .setExtras(BUNDLE) .build(); + reference.setCallingPackageName(packageName); // Parcel and unparcel. final Parcel parcel = Parcel.obtain(); @@ -87,10 +89,11 @@ public class TextSelectionTest { parcel.setDataPosition(0); final TextSelection.Request result = TextSelection.Request.CREATOR.createFromParcel(parcel); - assertEquals(text, result.getText()); + assertEquals(text, result.getText().toString()); assertEquals(0, result.getStartIndex()); assertEquals(text.length(), result.getEndIndex()); assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags()); assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY)); + assertEquals(packageName, result.getCallingPackageName()); } } diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java index e2618191235d..97f02cbc27e9 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java @@ -57,9 +57,9 @@ public class BinderCallsStatsTest { bcs.setSamplingInterval(5); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries(); assertEquals(1, uidEntries.size()); @@ -73,17 +73,17 @@ public class BinderCallsStatsTest { assertEquals(1, callStatsList.get(0).transactionCode); // CPU usage is sampled, should not be tracked here. - callSession = bcs.callStarted(binder, 1); + callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 20; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); assertEquals(2, uidEntry.callCount); assertEquals(1, uidEntry.recordedCallCount); assertEquals(10, uidEntry.cpuTimeMicros); assertEquals(1, callStatsList.size()); - callSession = bcs.callStarted(binder, 2); + callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID); bcs.time += 50; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); uidEntry = bcs.getUidEntries().get(WORKSOURCE_UID); assertEquals(3, uidEntry.callCount); assertEquals(1, uidEntry.recordedCallCount); @@ -98,9 +98,9 @@ public class BinderCallsStatsTest { bcs.setDetailedTracking(true); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries(); assertEquals(1, uidEntries.size()); @@ -116,9 +116,9 @@ public class BinderCallsStatsTest { assertEquals(binder.getClass(), callStatsList.get(0).binderClass); assertEquals(1, callStatsList.get(0).transactionCode); - callSession = bcs.callStarted(binder, 1); + callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 20; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); uidEntry = bcs.getUidEntries().get(WORKSOURCE_UID); assertEquals(2, uidEntry.callCount); @@ -126,9 +126,9 @@ public class BinderCallsStatsTest { callStatsList = new ArrayList(uidEntry.getCallStatsList()); assertEquals(1, callStatsList.size()); - callSession = bcs.callStarted(binder, 2); + callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID); bcs.time += 50; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); uidEntry = bcs.getUidEntries().get(WORKSOURCE_UID); assertEquals(3, uidEntry.callCount); @@ -142,7 +142,7 @@ public class BinderCallsStatsTest { public void testEnableInBetweenCall() { TestBinderCallsStats bcs = new TestBinderCallsStats(); Binder binder = new Binder(); - bcs.callEnded(null, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(null, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries(); assertEquals(0, uidEntries.size()); @@ -153,7 +153,7 @@ public class BinderCallsStatsTest { TestBinderCallsStats bcs = new TestBinderCallsStats(); Binder binder = new Binder(); bcs.callThrewException(null, new IllegalStateException()); - bcs.callEnded(null, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(null, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries(); assertEquals(0, uidEntries.size()); @@ -166,17 +166,17 @@ public class BinderCallsStatsTest { bcs.setSamplingInterval(2); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); - callSession = bcs.callStarted(binder, 1); + callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 1000; // shoud be ignored. - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); - callSession = bcs.callStarted(binder, 1); + callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 50; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries(); assertEquals(1, uidEntries.size()); @@ -203,13 +203,13 @@ public class BinderCallsStatsTest { bcs.setSamplingInterval(2); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); - callSession = bcs.callStarted(binder, 2 /* another method */); + callSession = bcs.callStarted(binder, 2 /* another method */, WORKSOURCE_UID); bcs.time += 1000; // shoud be ignored. - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries(); @@ -246,9 +246,9 @@ public class BinderCallsStatsTest { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); Binder binder = new BinderWithGetTransactionName(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); List<BinderCallsStats.ExportedCallStat> callStatsList = bcs.getExportedCallStats(); @@ -261,18 +261,18 @@ public class BinderCallsStatsTest { bcs.setDetailedTracking(true); Binder binder = new AnotherBinderWithGetTransactionName(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); Binder binder2 = new BinderWithGetTransactionName(); - callSession = bcs.callStarted(binder2, 1); + callSession = bcs.callStarted(binder2, 1, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); - callSession = bcs.callStarted(binder, 2); + callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); List<BinderCallsStats.ExportedCallStat> callStatsList = bcs.getExportedCallStats(); @@ -292,9 +292,9 @@ public class BinderCallsStatsTest { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); List<BinderCallsStats.ExportedCallStat> callStatsList = bcs.getExportedCallStats(); @@ -306,9 +306,9 @@ public class BinderCallsStatsTest { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); List<BinderCallsStats.CallStat> callStatsList = new ArrayList(bcs.getUidEntries().get(WORKSOURCE_UID).getCallStatsList()); @@ -322,13 +322,13 @@ public class BinderCallsStatsTest { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 50; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); - callSession = bcs.callStarted(binder, 1); + callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); List<BinderCallsStats.CallStat> callStatsList = new ArrayList(bcs.getUidEntries().get(WORKSOURCE_UID).getCallStatsList()); @@ -341,13 +341,13 @@ public class BinderCallsStatsTest { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.elapsedTime += 5; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); - callSession = bcs.callStarted(binder, 1); + callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.elapsedTime += 1; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); List<BinderCallsStats.CallStat> callStatsList = new ArrayList(bcs.getUidEntries().get(WORKSOURCE_UID).getCallStatsList()); @@ -368,17 +368,17 @@ public class BinderCallsStatsTest { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.callThrewException(callSession, new IllegalStateException()); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); - callSession = bcs.callStarted(binder, 1); + callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.callThrewException(callSession, new IllegalStateException()); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); - callSession = bcs.callStarted(binder, 1); + callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.callThrewException(callSession, new RuntimeException()); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); ArrayMap<String, Integer> expected = new ArrayMap<>(); expected.put("java.lang.IllegalStateException", 2); @@ -391,9 +391,9 @@ public class BinderCallsStatsTest { TestBinderCallsStats bcs = new TestBinderCallsStats(null); bcs.setDetailedTracking(true); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); bcs.setDeviceState(mDeviceState.getReadonlyClient()); @@ -408,8 +408,8 @@ public class BinderCallsStatsTest { mDeviceState.setCharging(true); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); assertEquals(0, bcs.getUidEntries().size()); } @@ -420,8 +420,8 @@ public class BinderCallsStatsTest { bcs.setDetailedTracking(true); mDeviceState.setScreenInteractive(false); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries(); assertEquals(1, uidEntries.size()); @@ -437,8 +437,8 @@ public class BinderCallsStatsTest { bcs.setDetailedTracking(true); mDeviceState.setScreenInteractive(true); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); SparseArray<BinderCallsStats.UidEntry> uidEntries = bcs.getUidEntries(); assertEquals(1, uidEntries.size()); @@ -455,8 +455,8 @@ public class BinderCallsStatsTest { mDeviceState.setCharging(true); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); assertEquals(0, bcs.getExportedCallStats().size()); } @@ -468,8 +468,8 @@ public class BinderCallsStatsTest { mDeviceState.setCharging(false); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); assertEquals(1, bcs.getExportedCallStats().size()); } @@ -479,12 +479,12 @@ public class BinderCallsStatsTest { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(true); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.callThrewException(callSession, new IllegalStateException()); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); PrintWriter pw = new PrintWriter(new StringWriter()); - bcs.dump(pw, new HashMap<>(), true); + bcs.dump(pw, new AppIdToPackageMap(new HashMap<>()), true); } @Test @@ -492,8 +492,8 @@ public class BinderCallsStatsTest { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDetailedTracking(false); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); assertEquals(0, bcs.getExportedCallStats().size()); } @@ -504,10 +504,10 @@ public class BinderCallsStatsTest { bcs.setDetailedTracking(true); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; bcs.elapsedTime += 20; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); assertEquals(1, bcs.getExportedCallStats().size()); BinderCallsStats.ExportedCallStat stat = bcs.getExportedCallStats().get(0); @@ -549,15 +549,15 @@ public class BinderCallsStatsTest { bcs.setMaxBinderCallStats(2); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); - callSession = bcs.callStarted(binder, 1); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); - callSession = bcs.callStarted(binder, 1); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); BinderCallsStats.UidEntry uidEntry = bcs.getUidEntries().get(WORKSOURCE_UID); List<BinderCallsStats.CallStat> callStatsList = new ArrayList(uidEntry.getCallStatsList()); @@ -574,12 +574,15 @@ public class BinderCallsStatsTest { bcs.setMaxBinderCallStats(1); Binder binder = new Binder(); - CallSession callSession = bcs.callStarted(binder, 1); - bcs.time += 10; - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); + + callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); - callSession = bcs.callStarted(binder, 2); - bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); + // Should use the same overflow entry. + callSession = bcs.callStarted(binder, 3, WORKSOURCE_UID); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); List<BinderCallsStats.ExportedCallStat> callStatsList = bcs.getExportedCallStats(); assertEquals(2, callStatsList.size()); @@ -590,11 +593,46 @@ public class BinderCallsStatsTest { assertEquals(CALLING_UID, callStats.callingUid); callStats = callStatsList.get(1); - assertEquals(1, callStats.callCount); + assertEquals(2, callStats.callCount); assertEquals("-1", callStats.methodName); assertEquals("com.android.internal.os.BinderCallsStats$OverflowBinder", callStats.className); - assertEquals(CALLING_UID, callStats.callingUid); + assertEquals(false , callStats.screenInteractive); + assertEquals(-1 , callStats.callingUid); + } + + @Test + public void testOverflow_oneOverflowEntryPerUid() { + TestBinderCallsStats bcs = new TestBinderCallsStats(); + bcs.setDetailedTracking(true); + bcs.setSamplingInterval(1); + bcs.setMaxBinderCallStats(1); + + Binder binder = new Binder(); + CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID); + + callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID + 1); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID + 1); + + // Different uids have different overflow entries. + callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID + 2); + bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID + 2); + + List<BinderCallsStats.ExportedCallStat> callStatsList = bcs.getExportedCallStats(); + assertEquals(3, callStatsList.size()); + + BinderCallsStats.ExportedCallStat callStats = callStatsList.get(1); + assertEquals(WORKSOURCE_UID + 1, callStats.workSourceUid); + assertEquals(1, callStats.callCount); + assertEquals("com.android.internal.os.BinderCallsStats$OverflowBinder", + callStats.className); + + callStats = callStatsList.get(2); + assertEquals(WORKSOURCE_UID + 2, callStats.workSourceUid); + assertEquals(1, callStats.callCount); + assertEquals("com.android.internal.os.BinderCallsStats$OverflowBinder", + callStats.className); } @Test @@ -620,7 +658,6 @@ public class BinderCallsStatsTest { class TestBinderCallsStats extends BinderCallsStats { public int callingUid = CALLING_UID; - public int workSourceUid = WORKSOURCE_UID; public long time = 1234; public long elapsedTime = 0; @@ -662,11 +699,6 @@ public class BinderCallsStatsTest { protected int getCallingUid() { return callingUid; } - - @Override - protected int getWorkSourceUid() { - return workSourceUid; - } } } diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 141948f1d25c..4a2db0a6bb54 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -225,15 +225,18 @@ <library name="android.test.base" file="/system/framework/android.test.base.impl.jar" /> <library name="android.test.mock" - file="/system/framework/android.test.mock.impl.jar" /> + file="/system/framework/android.test.mock.impl.jar" + dependency="android.test.base" /> <library name="android.test.runner" - file="/system/framework/android.test.runner.impl.jar" /> + file="/system/framework/android.test.runner.impl.jar" + dependency="android.test.base:android.test.mock" /> <!-- In BOOT_JARS historically, and now added to legacy applications. --> <library name="android.hidl.base-V1.0-java" file="/system/framework/android.hidl.base-V1.0-java.jar" /> <library name="android.hidl.manager-V1.0-java" - file="/system/framework/android.hidl.manager-V1.0-java.jar" /> + file="/system/framework/android.hidl.manager-V1.0-java.jar" + dependency="android.hidl.base-V1.0-java" /> <!-- These are the standard packages that are white-listed to always have internet access while in power save mode, even if they aren't in the foreground. --> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index f2373446924a..0a2f057ccb69 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -141,6 +141,7 @@ applications that come with the platform <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.UPDATE_APP_OPS_STATS"/> <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> + <permission name="android.permission.CLEAR_APP_USER_DATA"/> </privapp-permissions> <privapp-permissions package="com.android.permissioncontroller"> diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java index 4ee45bfd8bc8..70c90eb05b17 100644 --- a/graphics/java/android/graphics/drawable/DrawableWrapper.java +++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java @@ -65,7 +65,7 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb */ public DrawableWrapper(@Nullable Drawable dr) { mState = null; - mDrawable = dr; + setDrawable(dr); } /** diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 6f786517ba12..2536619bb7c8 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -19,6 +19,7 @@ package android.graphics.text; import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.Px; import android.graphics.Paint; import android.graphics.Rect; @@ -49,12 +50,17 @@ import libcore.util.NativeAllocationRegistry; */ public class MeasuredText { private long mNativePtr; + private boolean mComputeHyphenation; + private boolean mComputeLayout; private @NonNull char[] mChars; // Use builder instead. - private MeasuredText(long ptr, @NonNull char[] chars) { + private MeasuredText(long ptr, @NonNull char[] chars, boolean computeHyphenation, + boolean computeLayout) { mNativePtr = ptr; mChars = chars; + mComputeHyphenation = computeHyphenation; + mComputeLayout = computeLayout; } /** @@ -172,6 +178,7 @@ public class MeasuredText { private boolean mComputeHyphenation = false; private boolean mComputeLayout = true; private int mCurrentOffset = 0; + private @Nullable MeasuredText mHintMt = null; /** * Construct a builder. @@ -188,6 +195,27 @@ public class MeasuredText { } /** + * Construct a builder with existing MeasuredText. + * + * The MeasuredText returned by build method will hold a reference of the text. Developer is + * not supposed to modify the text. + * + * @param text a text + */ + public Builder(@NonNull MeasuredText text) { + Preconditions.checkNotNull(text); + mText = text.mChars; + mNativePtr = nInitBuilder(); + if (!text.mComputeLayout) { + throw new IllegalArgumentException( + "The input MeasuredText must not be created with setComputeLayout(false)."); + } + mComputeHyphenation = text.mComputeHyphenation; + mComputeLayout = text.mComputeLayout; + mHintMt = text; + } + + /** * Apply styles to the given length. * * Keeps an internal offset which increases at every append. The initial value for this @@ -282,10 +310,16 @@ public class MeasuredText { if (mCurrentOffset != mText.length) { throw new IllegalStateException("Style info has not been provided for all text."); } + if (mHintMt != null && mHintMt.mComputeHyphenation != mComputeHyphenation) { + throw new IllegalArgumentException( + "The hyphenation configuration is different from given hint MeasuredText"); + } try { - long ptr = nBuildMeasuredText(mNativePtr, mText, mComputeHyphenation, + long hintPtr = (mHintMt == null) ? 0 : mHintMt.getNativePtr(); + long ptr = nBuildMeasuredText(mNativePtr, hintPtr, mText, mComputeHyphenation, + mComputeLayout); + final MeasuredText res = new MeasuredText(ptr, mText, mComputeHyphenation, mComputeLayout); - MeasuredText res = new MeasuredText(ptr, mText); sRegistry.registerNativeAllocation(res, ptr); return res; } finally { @@ -339,6 +373,7 @@ public class MeasuredText { private static native long nBuildMeasuredText( /* Non Zero */ long nativeBuilderPtr, + long hintMtPtr, @NonNull char[] text, boolean computeHyphenation, boolean computeLayout); diff --git a/libs/androidfw/LocaleData.cpp b/libs/androidfw/LocaleData.cpp index 889d166d853b..020cef6012e9 100644 --- a/libs/androidfw/LocaleData.cpp +++ b/libs/androidfw/LocaleData.cpp @@ -34,11 +34,11 @@ inline uint32_t packLocale(const char* language, const char* region) { } inline uint32_t dropRegion(uint32_t packed_locale) { - return packed_locale & 0xFFFF0000lu; + return packed_locale & 0xFFFF0000LU; } inline bool hasRegion(uint32_t packed_locale) { - return (packed_locale & 0x0000FFFFlu) != 0; + return (packed_locale & 0x0000FFFFLU) != 0; } const size_t SCRIPT_LENGTH = 4; @@ -122,9 +122,9 @@ inline bool isRepresentative(uint32_t language_and_region, const char* script) { return (REPRESENTATIVE_LOCALES.count(packed_locale) != 0); } -const uint32_t US_SPANISH = 0x65735553lu; // es-US -const uint32_t MEXICAN_SPANISH = 0x65734D58lu; // es-MX -const uint32_t LATIN_AMERICAN_SPANISH = 0x6573A424lu; // es-419 +const uint32_t US_SPANISH = 0x65735553LU; // es-US +const uint32_t MEXICAN_SPANISH = 0x65734D58LU; // es-MX +const uint32_t LATIN_AMERICAN_SPANISH = 0x6573A424LU; // es-419 // The two locales es-US and es-MX are treated as special fallbacks for es-419. // If there is no es-419, they are considered its equivalent. @@ -225,8 +225,8 @@ void localeDataComputeScript(char out[4], const char* language, const char* regi } const uint32_t ENGLISH_STOP_LIST[2] = { - 0x656E0000lu, // en - 0x656E8400lu, // en-001 + 0x656E0000LU, // en + 0x656E8400LU, // en-001 }; const char ENGLISH_CHARS[2] = {'e', 'n'}; const char LATIN_CHARS[4] = {'L', 'a', 't', 'n'}; diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp index 7c381efec7ec..c276a238e2e1 100644 --- a/libs/androidfw/LocaleDataTables.cpp +++ b/libs/androidfw/LocaleDataTables.cpp @@ -1446,733 +1446,733 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ }); std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ - 0x616145544C61746Ellu, // aa_Latn_ET - 0x616247454379726Cllu, // ab_Cyrl_GE - 0xC42047484C61746Ellu, // abr_Latn_GH - 0x904049444C61746Ellu, // ace_Latn_ID - 0x9C4055474C61746Ellu, // ach_Latn_UG - 0x806047484C61746Ellu, // ada_Latn_GH - 0xE06052554379726Cllu, // ady_Cyrl_RU - 0x6165495241767374llu, // ae_Avst_IR - 0x8480544E41726162llu, // aeb_Arab_TN - 0x61665A414C61746Ellu, // af_Latn_ZA - 0xC0C0434D4C61746Ellu, // agq_Latn_CM - 0xB8E0494E41686F6Dllu, // aho_Ahom_IN - 0x616B47484C61746Ellu, // ak_Latn_GH - 0xA940495158737578llu, // akk_Xsux_IQ - 0xB560584B4C61746Ellu, // aln_Latn_XK - 0xCD6052554379726Cllu, // alt_Cyrl_RU - 0x616D455445746869llu, // am_Ethi_ET - 0xB9804E474C61746Ellu, // amo_Latn_NG - 0xE5C049444C61746Ellu, // aoz_Latn_ID - 0x8DE0544741726162llu, // apd_Arab_TG - 0x6172454741726162llu, // ar_Arab_EG - 0x8A20495241726D69llu, // arc_Armi_IR - 0x8A204A4F4E626174llu, // arc_Nbat_JO - 0x8A20535950616C6Dllu, // arc_Palm_SY - 0xB620434C4C61746Ellu, // arn_Latn_CL - 0xBA20424F4C61746Ellu, // aro_Latn_BO - 0xC220445A41726162llu, // arq_Arab_DZ - 0xE2204D4141726162llu, // ary_Arab_MA - 0xE620454741726162llu, // arz_Arab_EG - 0x6173494E42656E67llu, // as_Beng_IN - 0x8240545A4C61746Ellu, // asa_Latn_TZ - 0x9240555353676E77llu, // ase_Sgnw_US - 0xCE4045534C61746Ellu, // ast_Latn_ES - 0xA66043414C61746Ellu, // atj_Latn_CA - 0x617652554379726Cllu, // av_Cyrl_RU - 0x82C0494E44657661llu, // awa_Deva_IN - 0x6179424F4C61746Ellu, // ay_Latn_BO - 0x617A495241726162llu, // az_Arab_IR - 0x617A415A4C61746Ellu, // az_Latn_AZ - 0x626152554379726Cllu, // ba_Cyrl_RU - 0xAC01504B41726162llu, // bal_Arab_PK - 0xB40149444C61746Ellu, // ban_Latn_ID - 0xBC014E5044657661llu, // bap_Deva_NP - 0xC40141544C61746Ellu, // bar_Latn_AT - 0xC801434D4C61746Ellu, // bas_Latn_CM - 0xDC01434D42616D75llu, // bax_Bamu_CM - 0x882149444C61746Ellu, // bbc_Latn_ID - 0xA421434D4C61746Ellu, // bbj_Latn_CM - 0xA04143494C61746Ellu, // bci_Latn_CI - 0x626542594379726Cllu, // be_Cyrl_BY - 0xA481534441726162llu, // bej_Arab_SD - 0xB0815A4D4C61746Ellu, // bem_Latn_ZM - 0xD88149444C61746Ellu, // bew_Latn_ID - 0xE481545A4C61746Ellu, // bez_Latn_TZ - 0x8CA1434D4C61746Ellu, // bfd_Latn_CM - 0xC0A1494E54616D6Cllu, // bfq_Taml_IN - 0xCCA1504B41726162llu, // bft_Arab_PK - 0xE0A1494E44657661llu, // bfy_Deva_IN - 0x626742474379726Cllu, // bg_Cyrl_BG - 0x88C1494E44657661llu, // bgc_Deva_IN - 0xB4C1504B41726162llu, // bgn_Arab_PK - 0xDCC154524772656Bllu, // bgx_Grek_TR - 0x84E1494E44657661llu, // bhb_Deva_IN - 0xA0E1494E44657661llu, // bhi_Deva_IN - 0xA8E150484C61746Ellu, // bhk_Latn_PH - 0xB8E1494E44657661llu, // bho_Deva_IN - 0x626956554C61746Ellu, // bi_Latn_VU - 0xA90150484C61746Ellu, // bik_Latn_PH - 0xB5014E474C61746Ellu, // bin_Latn_NG - 0xA521494E44657661llu, // bjj_Deva_IN - 0xB52149444C61746Ellu, // bjn_Latn_ID - 0xB141434D4C61746Ellu, // bkm_Latn_CM - 0xD14150484C61746Ellu, // bku_Latn_PH - 0xCD61564E54617674llu, // blt_Tavt_VN - 0x626D4D4C4C61746Ellu, // bm_Latn_ML - 0xC1814D4C4C61746Ellu, // bmq_Latn_ML - 0x626E424442656E67llu, // bn_Beng_BD - 0x626F434E54696274llu, // bo_Tibt_CN - 0xE1E1494E42656E67llu, // bpy_Beng_IN - 0xA201495241726162llu, // bqi_Arab_IR - 0xD60143494C61746Ellu, // bqv_Latn_CI - 0x627246524C61746Ellu, // br_Latn_FR - 0x8221494E44657661llu, // bra_Deva_IN - 0x9E21504B41726162llu, // brh_Arab_PK - 0xDE21494E44657661llu, // brx_Deva_IN - 0x627342414C61746Ellu, // bs_Latn_BA - 0xC2414C5242617373llu, // bsq_Bass_LR - 0xCA41434D4C61746Ellu, // bss_Latn_CM - 0xBA6150484C61746Ellu, // bto_Latn_PH - 0xD661504B44657661llu, // btv_Deva_PK - 0x828152554379726Cllu, // bua_Cyrl_RU - 0x8A8159544C61746Ellu, // buc_Latn_YT - 0x9A8149444C61746Ellu, // bug_Latn_ID - 0xB281434D4C61746Ellu, // bum_Latn_CM - 0x86A147514C61746Ellu, // bvb_Latn_GQ - 0xB701455245746869llu, // byn_Ethi_ER - 0xD701434D4C61746Ellu, // byv_Latn_CM - 0x93214D4C4C61746Ellu, // bze_Latn_ML - 0x636145534C61746Ellu, // ca_Latn_ES - 0x9C424E474C61746Ellu, // cch_Latn_NG - 0xBC42494E42656E67llu, // ccp_Beng_IN - 0xBC42424443616B6Dllu, // ccp_Cakm_BD - 0x636552554379726Cllu, // ce_Cyrl_RU - 0x848250484C61746Ellu, // ceb_Latn_PH - 0x98C255474C61746Ellu, // cgg_Latn_UG - 0x636847554C61746Ellu, // ch_Latn_GU - 0xA8E2464D4C61746Ellu, // chk_Latn_FM - 0xB0E252554379726Cllu, // chm_Cyrl_RU - 0xB8E255534C61746Ellu, // cho_Latn_US - 0xBCE243414C61746Ellu, // chp_Latn_CA - 0xC4E2555343686572llu, // chr_Cher_US - 0x81224B4841726162llu, // cja_Arab_KH - 0xB122564E4368616Dllu, // cjm_Cham_VN - 0x8542495141726162llu, // ckb_Arab_IQ - 0x636F46524C61746Ellu, // co_Latn_FR - 0xBDC24547436F7074llu, // cop_Copt_EG - 0xC9E250484C61746Ellu, // cps_Latn_PH - 0x6372434143616E73llu, // cr_Cans_CA - 0xA622434143616E73llu, // crj_Cans_CA - 0xAA22434143616E73llu, // crk_Cans_CA - 0xAE22434143616E73llu, // crl_Cans_CA - 0xB222434143616E73llu, // crm_Cans_CA - 0xCA2253434C61746Ellu, // crs_Latn_SC - 0x6373435A4C61746Ellu, // cs_Latn_CZ - 0x8642504C4C61746Ellu, // csb_Latn_PL - 0xDA42434143616E73llu, // csw_Cans_CA - 0x8E624D4D50617563llu, // ctd_Pauc_MM - 0x637552554379726Cllu, // cu_Cyrl_RU - 0x63754247476C6167llu, // cu_Glag_BG - 0x637652554379726Cllu, // cv_Cyrl_RU - 0x637947424C61746Ellu, // cy_Latn_GB - 0x6461444B4C61746Ellu, // da_Latn_DK - 0xA80355534C61746Ellu, // dak_Latn_US - 0xC40352554379726Cllu, // dar_Cyrl_RU - 0xD4034B454C61746Ellu, // dav_Latn_KE - 0x8843494E41726162llu, // dcc_Arab_IN - 0x646544454C61746Ellu, // de_Latn_DE - 0xB48343414C61746Ellu, // den_Latn_CA - 0xC4C343414C61746Ellu, // dgr_Latn_CA - 0x91234E454C61746Ellu, // dje_Latn_NE - 0xA5A343494C61746Ellu, // dnj_Latn_CI - 0xA1C3494E41726162llu, // doi_Arab_IN - 0x864344454C61746Ellu, // dsb_Latn_DE - 0xB2634D4C4C61746Ellu, // dtm_Latn_ML - 0xBE634D594C61746Ellu, // dtp_Latn_MY - 0xE2634E5044657661llu, // dty_Deva_NP - 0x8283434D4C61746Ellu, // dua_Latn_CM - 0x64764D5654686161llu, // dv_Thaa_MV - 0xBB03534E4C61746Ellu, // dyo_Latn_SN - 0xD30342464C61746Ellu, // dyu_Latn_BF - 0x647A425454696274llu, // dz_Tibt_BT - 0xD0244B454C61746Ellu, // ebu_Latn_KE - 0x656547484C61746Ellu, // ee_Latn_GH - 0xA0A44E474C61746Ellu, // efi_Latn_NG - 0xACC449544C61746Ellu, // egl_Latn_IT - 0xE0C4454745677970llu, // egy_Egyp_EG - 0xE1444D4D4B616C69llu, // eky_Kali_MM - 0x656C47524772656Bllu, // el_Grek_GR - 0x656E47424C61746Ellu, // en_Latn_GB - 0x656E55534C61746Ellu, // en_Latn_US - 0x656E474253686177llu, // en_Shaw_GB - 0x657345534C61746Ellu, // es_Latn_ES - 0x65734D584C61746Ellu, // es_Latn_MX - 0x657355534C61746Ellu, // es_Latn_US - 0xD24455534C61746Ellu, // esu_Latn_US - 0x657445454C61746Ellu, // et_Latn_EE - 0xCE6449544974616Cllu, // ett_Ital_IT - 0x657545534C61746Ellu, // eu_Latn_ES - 0xBAC4434D4C61746Ellu, // ewo_Latn_CM - 0xCEE445534C61746Ellu, // ext_Latn_ES - 0x6661495241726162llu, // fa_Arab_IR - 0xB40547514C61746Ellu, // fan_Latn_GQ - 0x6666474E41646C6Dllu, // ff_Adlm_GN - 0x6666534E4C61746Ellu, // ff_Latn_SN - 0xB0A54D4C4C61746Ellu, // ffm_Latn_ML - 0x666946494C61746Ellu, // fi_Latn_FI - 0x8105534441726162llu, // fia_Arab_SD - 0xAD0550484C61746Ellu, // fil_Latn_PH - 0xCD0553454C61746Ellu, // fit_Latn_SE - 0x666A464A4C61746Ellu, // fj_Latn_FJ - 0x666F464F4C61746Ellu, // fo_Latn_FO - 0xB5C5424A4C61746Ellu, // fon_Latn_BJ - 0x667246524C61746Ellu, // fr_Latn_FR - 0x8A2555534C61746Ellu, // frc_Latn_US - 0xBE2546524C61746Ellu, // frp_Latn_FR - 0xC62544454C61746Ellu, // frr_Latn_DE - 0xCA2544454C61746Ellu, // frs_Latn_DE - 0x8685434D41726162llu, // fub_Arab_CM - 0x8E8557464C61746Ellu, // fud_Latn_WF - 0x9685474E4C61746Ellu, // fuf_Latn_GN - 0xC2854E454C61746Ellu, // fuq_Latn_NE - 0xC68549544C61746Ellu, // fur_Latn_IT - 0xD6854E474C61746Ellu, // fuv_Latn_NG - 0xC6A553444C61746Ellu, // fvr_Latn_SD - 0x66794E4C4C61746Ellu, // fy_Latn_NL - 0x676149454C61746Ellu, // ga_Latn_IE - 0x800647484C61746Ellu, // gaa_Latn_GH - 0x98064D444C61746Ellu, // gag_Latn_MD - 0xB406434E48616E73llu, // gan_Hans_CN - 0xE00649444C61746Ellu, // gay_Latn_ID - 0xB026494E44657661llu, // gbm_Deva_IN - 0xE426495241726162llu, // gbz_Arab_IR - 0xC44647464C61746Ellu, // gcr_Latn_GF - 0x676447424C61746Ellu, // gd_Latn_GB - 0xE486455445746869llu, // gez_Ethi_ET - 0xB4C64E5044657661llu, // ggn_Deva_NP - 0xAD064B494C61746Ellu, // gil_Latn_KI - 0xA926504B41726162llu, // gjk_Arab_PK - 0xD126504B41726162llu, // gju_Arab_PK - 0x676C45534C61746Ellu, // gl_Latn_ES - 0xA966495241726162llu, // glk_Arab_IR - 0x676E50594C61746Ellu, // gn_Latn_PY - 0xB1C6494E44657661llu, // gom_Deva_IN - 0xB5C6494E54656C75llu, // gon_Telu_IN - 0xC5C649444C61746Ellu, // gor_Latn_ID - 0xC9C64E4C4C61746Ellu, // gos_Latn_NL - 0xCDC65541476F7468llu, // got_Goth_UA - 0x8A26435943707274llu, // grc_Cprt_CY - 0x8A2647524C696E62llu, // grc_Linb_GR - 0xCE26494E42656E67llu, // grt_Beng_IN - 0xDA4643484C61746Ellu, // gsw_Latn_CH - 0x6775494E47756A72llu, // gu_Gujr_IN - 0x868642524C61746Ellu, // gub_Latn_BR - 0x8A86434F4C61746Ellu, // guc_Latn_CO - 0xC68647484C61746Ellu, // gur_Latn_GH - 0xE6864B454C61746Ellu, // guz_Latn_KE - 0x6776494D4C61746Ellu, // gv_Latn_IM - 0xC6A64E5044657661llu, // gvr_Deva_NP - 0xA2C643414C61746Ellu, // gwi_Latn_CA - 0x68614E474C61746Ellu, // ha_Latn_NG - 0xA807434E48616E73llu, // hak_Hans_CN - 0xD80755534C61746Ellu, // haw_Latn_US - 0xE407414641726162llu, // haz_Arab_AF - 0x6865494C48656272llu, // he_Hebr_IL - 0x6869494E44657661llu, // hi_Deva_IN - 0x9507464A4C61746Ellu, // hif_Latn_FJ - 0xAD0750484C61746Ellu, // hil_Latn_PH - 0xD1675452486C7577llu, // hlu_Hluw_TR - 0x8D87434E506C7264llu, // hmd_Plrd_CN - 0x8DA7504B41726162llu, // hnd_Arab_PK - 0x91A7494E44657661llu, // hne_Deva_IN - 0xA5A74C41486D6E67llu, // hnj_Hmng_LA - 0xB5A750484C61746Ellu, // hnn_Latn_PH - 0xB9A7504B41726162llu, // hno_Arab_PK - 0x686F50474C61746Ellu, // ho_Latn_PG - 0x89C7494E44657661llu, // hoc_Deva_IN - 0xA5C7494E44657661llu, // hoj_Deva_IN - 0x687248524C61746Ellu, // hr_Latn_HR - 0x864744454C61746Ellu, // hsb_Latn_DE - 0xB647434E48616E73llu, // hsn_Hans_CN - 0x687448544C61746Ellu, // ht_Latn_HT - 0x687548554C61746Ellu, // hu_Latn_HU - 0x6879414D41726D6Ellu, // hy_Armn_AM - 0x687A4E414C61746Ellu, // hz_Latn_NA - 0x696146524C61746Ellu, // ia_Latn_FR - 0x80284D594C61746Ellu, // iba_Latn_MY - 0x84284E474C61746Ellu, // ibb_Latn_NG - 0x696449444C61746Ellu, // id_Latn_ID - 0x69674E474C61746Ellu, // ig_Latn_NG - 0x6969434E59696969llu, // ii_Yiii_CN - 0x696B55534C61746Ellu, // ik_Latn_US - 0xCD4843414C61746Ellu, // ikt_Latn_CA - 0xB96850484C61746Ellu, // ilo_Latn_PH - 0x696E49444C61746Ellu, // in_Latn_ID - 0x9DA852554379726Cllu, // inh_Cyrl_RU - 0x697349534C61746Ellu, // is_Latn_IS - 0x697449544C61746Ellu, // it_Latn_IT - 0x6975434143616E73llu, // iu_Cans_CA - 0x6977494C48656272llu, // iw_Hebr_IL - 0x9F2852554C61746Ellu, // izh_Latn_RU - 0x6A614A504A70616Ellu, // ja_Jpan_JP - 0xB0094A4D4C61746Ellu, // jam_Latn_JM - 0xB8C9434D4C61746Ellu, // jgo_Latn_CM - 0x8989545A4C61746Ellu, // jmc_Latn_TZ - 0xAD894E5044657661llu, // jml_Deva_NP - 0xCE89444B4C61746Ellu, // jut_Latn_DK - 0x6A7649444C61746Ellu, // jv_Latn_ID - 0x6A7749444C61746Ellu, // jw_Latn_ID - 0x6B61474547656F72llu, // ka_Geor_GE - 0x800A555A4379726Cllu, // kaa_Cyrl_UZ - 0x840A445A4C61746Ellu, // kab_Latn_DZ - 0x880A4D4D4C61746Ellu, // kac_Latn_MM - 0xA40A4E474C61746Ellu, // kaj_Latn_NG - 0xB00A4B454C61746Ellu, // kam_Latn_KE - 0xB80A4D4C4C61746Ellu, // kao_Latn_ML - 0x8C2A52554379726Cllu, // kbd_Cyrl_RU - 0xE02A4E4541726162llu, // kby_Arab_NE - 0x984A4E474C61746Ellu, // kcg_Latn_NG - 0xA84A5A574C61746Ellu, // kck_Latn_ZW - 0x906A545A4C61746Ellu, // kde_Latn_TZ - 0x9C6A544741726162llu, // kdh_Arab_TG - 0xCC6A544854686169llu, // kdt_Thai_TH - 0x808A43564C61746Ellu, // kea_Latn_CV - 0xB48A434D4C61746Ellu, // ken_Latn_CM - 0xB8AA43494C61746Ellu, // kfo_Latn_CI - 0xC4AA494E44657661llu, // kfr_Deva_IN - 0xE0AA494E44657661llu, // kfy_Deva_IN - 0x6B6743444C61746Ellu, // kg_Latn_CD - 0x90CA49444C61746Ellu, // kge_Latn_ID - 0xBCCA42524C61746Ellu, // kgp_Latn_BR - 0x80EA494E4C61746Ellu, // kha_Latn_IN - 0x84EA434E54616C75llu, // khb_Talu_CN - 0xB4EA494E44657661llu, // khn_Deva_IN - 0xC0EA4D4C4C61746Ellu, // khq_Latn_ML - 0xCCEA494E4D796D72llu, // kht_Mymr_IN - 0xD8EA504B41726162llu, // khw_Arab_PK - 0x6B694B454C61746Ellu, // ki_Latn_KE - 0xD10A54524C61746Ellu, // kiu_Latn_TR - 0x6B6A4E414C61746Ellu, // kj_Latn_NA - 0x992A4C414C616F6Fllu, // kjg_Laoo_LA - 0x6B6B434E41726162llu, // kk_Arab_CN - 0x6B6B4B5A4379726Cllu, // kk_Cyrl_KZ - 0xA54A434D4C61746Ellu, // kkj_Latn_CM - 0x6B6C474C4C61746Ellu, // kl_Latn_GL - 0xB56A4B454C61746Ellu, // kln_Latn_KE - 0x6B6D4B484B686D72llu, // km_Khmr_KH - 0x858A414F4C61746Ellu, // kmb_Latn_AO - 0x6B6E494E4B6E6461llu, // kn_Knda_IN - 0x6B6F4B524B6F7265llu, // ko_Kore_KR - 0xA1CA52554379726Cllu, // koi_Cyrl_RU - 0xA9CA494E44657661llu, // kok_Deva_IN - 0xC9CA464D4C61746Ellu, // kos_Latn_FM - 0x91EA4C524C61746Ellu, // kpe_Latn_LR - 0x8A2A52554379726Cllu, // krc_Cyrl_RU - 0xA22A534C4C61746Ellu, // kri_Latn_SL - 0xA62A50484C61746Ellu, // krj_Latn_PH - 0xAE2A52554C61746Ellu, // krl_Latn_RU - 0xD22A494E44657661llu, // kru_Deva_IN - 0x6B73494E41726162llu, // ks_Arab_IN - 0x864A545A4C61746Ellu, // ksb_Latn_TZ - 0x964A434D4C61746Ellu, // ksf_Latn_CM - 0x9E4A44454C61746Ellu, // ksh_Latn_DE - 0x6B75495141726162llu, // ku_Arab_IQ - 0x6B7554524C61746Ellu, // ku_Latn_TR - 0xB28A52554379726Cllu, // kum_Cyrl_RU - 0x6B7652554379726Cllu, // kv_Cyrl_RU - 0xC6AA49444C61746Ellu, // kvr_Latn_ID - 0xDEAA504B41726162llu, // kvx_Arab_PK - 0x6B7747424C61746Ellu, // kw_Latn_GB - 0xB2EA544854686169llu, // kxm_Thai_TH - 0xBEEA504B41726162llu, // kxp_Arab_PK - 0x6B79434E41726162llu, // ky_Arab_CN - 0x6B794B474379726Cllu, // ky_Cyrl_KG - 0x6B7954524C61746Ellu, // ky_Latn_TR - 0x6C6156414C61746Ellu, // la_Latn_VA - 0x840B47524C696E61llu, // lab_Lina_GR - 0x8C0B494C48656272llu, // lad_Hebr_IL - 0x980B545A4C61746Ellu, // lag_Latn_TZ - 0x9C0B504B41726162llu, // lah_Arab_PK - 0xA40B55474C61746Ellu, // laj_Latn_UG - 0x6C624C554C61746Ellu, // lb_Latn_LU - 0x902B52554379726Cllu, // lbe_Cyrl_RU - 0xD82B49444C61746Ellu, // lbw_Latn_ID - 0xBC4B434E54686169llu, // lcp_Thai_CN - 0xBC8B494E4C657063llu, // lep_Lepc_IN - 0xE48B52554379726Cllu, // lez_Cyrl_RU - 0x6C6755474C61746Ellu, // lg_Latn_UG - 0x6C694E4C4C61746Ellu, // li_Latn_NL - 0x950B4E5044657661llu, // lif_Deva_NP - 0x950B494E4C696D62llu, // lif_Limb_IN - 0xA50B49544C61746Ellu, // lij_Latn_IT - 0xC90B434E4C697375llu, // lis_Lisu_CN - 0xBD2B49444C61746Ellu, // ljp_Latn_ID - 0xA14B495241726162llu, // lki_Arab_IR - 0xCD4B55534C61746Ellu, // lkt_Latn_US - 0xB58B494E54656C75llu, // lmn_Telu_IN - 0xB98B49544C61746Ellu, // lmo_Latn_IT - 0x6C6E43444C61746Ellu, // ln_Latn_CD - 0x6C6F4C414C616F6Fllu, // lo_Laoo_LA - 0xADCB43444C61746Ellu, // lol_Latn_CD - 0xE5CB5A4D4C61746Ellu, // loz_Latn_ZM - 0x8A2B495241726162llu, // lrc_Arab_IR - 0x6C744C544C61746Ellu, // lt_Latn_LT - 0x9A6B4C564C61746Ellu, // ltg_Latn_LV - 0x6C7543444C61746Ellu, // lu_Latn_CD - 0x828B43444C61746Ellu, // lua_Latn_CD - 0xBA8B4B454C61746Ellu, // luo_Latn_KE - 0xE28B4B454C61746Ellu, // luy_Latn_KE - 0xE68B495241726162llu, // luz_Arab_IR - 0x6C764C564C61746Ellu, // lv_Latn_LV - 0xAECB544854686169llu, // lwl_Thai_TH - 0x9F2B434E48616E73llu, // lzh_Hans_CN - 0xE72B54524C61746Ellu, // lzz_Latn_TR - 0x8C0C49444C61746Ellu, // mad_Latn_ID - 0x940C434D4C61746Ellu, // maf_Latn_CM - 0x980C494E44657661llu, // mag_Deva_IN - 0xA00C494E44657661llu, // mai_Deva_IN - 0xA80C49444C61746Ellu, // mak_Latn_ID - 0xB40C474D4C61746Ellu, // man_Latn_GM - 0xB40C474E4E6B6F6Fllu, // man_Nkoo_GN - 0xC80C4B454C61746Ellu, // mas_Latn_KE - 0xE40C4D584C61746Ellu, // maz_Latn_MX - 0x946C52554379726Cllu, // mdf_Cyrl_RU - 0x9C6C50484C61746Ellu, // mdh_Latn_PH - 0xC46C49444C61746Ellu, // mdr_Latn_ID - 0xB48C534C4C61746Ellu, // men_Latn_SL - 0xC48C4B454C61746Ellu, // mer_Latn_KE - 0x80AC544841726162llu, // mfa_Arab_TH - 0x90AC4D554C61746Ellu, // mfe_Latn_MU - 0x6D674D474C61746Ellu, // mg_Latn_MG - 0x9CCC4D5A4C61746Ellu, // mgh_Latn_MZ - 0xB8CC434D4C61746Ellu, // mgo_Latn_CM - 0xBCCC4E5044657661llu, // mgp_Deva_NP - 0xE0CC545A4C61746Ellu, // mgy_Latn_TZ - 0x6D684D484C61746Ellu, // mh_Latn_MH - 0x6D694E5A4C61746Ellu, // mi_Latn_NZ - 0xB50C49444C61746Ellu, // min_Latn_ID - 0xC90C495148617472llu, // mis_Hatr_IQ - 0x6D6B4D4B4379726Cllu, // mk_Cyrl_MK - 0x6D6C494E4D6C796Dllu, // ml_Mlym_IN - 0xC96C53444C61746Ellu, // mls_Latn_SD - 0x6D6E4D4E4379726Cllu, // mn_Cyrl_MN - 0x6D6E434E4D6F6E67llu, // mn_Mong_CN - 0xA1AC494E42656E67llu, // mni_Beng_IN - 0xD9AC4D4D4D796D72llu, // mnw_Mymr_MM - 0x91CC43414C61746Ellu, // moe_Latn_CA - 0x9DCC43414C61746Ellu, // moh_Latn_CA - 0xC9CC42464C61746Ellu, // mos_Latn_BF - 0x6D72494E44657661llu, // mr_Deva_IN - 0x8E2C4E5044657661llu, // mrd_Deva_NP - 0xA62C52554379726Cllu, // mrj_Cyrl_RU - 0xBA2C42444D726F6Fllu, // mro_Mroo_BD - 0x6D734D594C61746Ellu, // ms_Latn_MY - 0x6D744D544C61746Ellu, // mt_Latn_MT - 0xC66C494E44657661llu, // mtr_Deva_IN - 0x828C434D4C61746Ellu, // mua_Latn_CM - 0xCA8C55534C61746Ellu, // mus_Latn_US - 0xE2AC504B41726162llu, // mvy_Arab_PK - 0xAACC4D4C4C61746Ellu, // mwk_Latn_ML - 0xC6CC494E44657661llu, // mwr_Deva_IN - 0xD6CC49444C61746Ellu, // mwv_Latn_ID - 0x8AEC5A574C61746Ellu, // mxc_Latn_ZW - 0x6D794D4D4D796D72llu, // my_Mymr_MM - 0xD70C52554379726Cllu, // myv_Cyrl_RU - 0xDF0C55474C61746Ellu, // myx_Latn_UG - 0xE70C49524D616E64llu, // myz_Mand_IR - 0xB72C495241726162llu, // mzn_Arab_IR - 0x6E614E524C61746Ellu, // na_Latn_NR - 0xB40D434E48616E73llu, // nan_Hans_CN - 0xBC0D49544C61746Ellu, // nap_Latn_IT - 0xC00D4E414C61746Ellu, // naq_Latn_NA - 0x6E624E4F4C61746Ellu, // nb_Latn_NO - 0x9C4D4D584C61746Ellu, // nch_Latn_MX - 0x6E645A574C61746Ellu, // nd_Latn_ZW - 0x886D4D5A4C61746Ellu, // ndc_Latn_MZ - 0xC86D44454C61746Ellu, // nds_Latn_DE - 0x6E654E5044657661llu, // ne_Deva_NP - 0xD88D4E5044657661llu, // new_Deva_NP - 0x6E674E414C61746Ellu, // ng_Latn_NA - 0xACCD4D5A4C61746Ellu, // ngl_Latn_MZ - 0x90ED4D584C61746Ellu, // nhe_Latn_MX - 0xD8ED4D584C61746Ellu, // nhw_Latn_MX - 0xA50D49444C61746Ellu, // nij_Latn_ID - 0xD10D4E554C61746Ellu, // niu_Latn_NU - 0xB92D494E4C61746Ellu, // njo_Latn_IN - 0x6E6C4E4C4C61746Ellu, // nl_Latn_NL - 0x998D434D4C61746Ellu, // nmg_Latn_CM - 0x6E6E4E4F4C61746Ellu, // nn_Latn_NO - 0x9DAD434D4C61746Ellu, // nnh_Latn_CM - 0x6E6F4E4F4C61746Ellu, // no_Latn_NO - 0x8DCD54484C616E61llu, // nod_Lana_TH - 0x91CD494E44657661llu, // noe_Deva_IN - 0xB5CD534552756E72llu, // non_Runr_SE - 0xBA0D474E4E6B6F6Fllu, // nqo_Nkoo_GN - 0x6E725A414C61746Ellu, // nr_Latn_ZA - 0xAA4D434143616E73llu, // nsk_Cans_CA - 0xBA4D5A414C61746Ellu, // nso_Latn_ZA - 0xCA8D53534C61746Ellu, // nus_Latn_SS - 0x6E7655534C61746Ellu, // nv_Latn_US - 0xC2ED434E4C61746Ellu, // nxq_Latn_CN - 0x6E794D574C61746Ellu, // ny_Latn_MW - 0xB30D545A4C61746Ellu, // nym_Latn_TZ - 0xB70D55474C61746Ellu, // nyn_Latn_UG - 0xA32D47484C61746Ellu, // nzi_Latn_GH - 0x6F6346524C61746Ellu, // oc_Latn_FR - 0x6F6D45544C61746Ellu, // om_Latn_ET - 0x6F72494E4F727961llu, // or_Orya_IN - 0x6F7347454379726Cllu, // os_Cyrl_GE - 0x824E55534F736765llu, // osa_Osge_US - 0xAA6E4D4E4F726B68llu, // otk_Orkh_MN - 0x7061504B41726162llu, // pa_Arab_PK - 0x7061494E47757275llu, // pa_Guru_IN - 0x980F50484C61746Ellu, // pag_Latn_PH - 0xAC0F495250686C69llu, // pal_Phli_IR - 0xAC0F434E50686C70llu, // pal_Phlp_CN - 0xB00F50484C61746Ellu, // pam_Latn_PH - 0xBC0F41574C61746Ellu, // pap_Latn_AW - 0xD00F50574C61746Ellu, // pau_Latn_PW - 0x8C4F46524C61746Ellu, // pcd_Latn_FR - 0xB04F4E474C61746Ellu, // pcm_Latn_NG - 0x886F55534C61746Ellu, // pdc_Latn_US - 0xCC6F43414C61746Ellu, // pdt_Latn_CA - 0xB88F49525870656Fllu, // peo_Xpeo_IR - 0xACAF44454C61746Ellu, // pfl_Latn_DE - 0xB4EF4C4250686E78llu, // phn_Phnx_LB - 0x814F494E42726168llu, // pka_Brah_IN - 0xB94F4B454C61746Ellu, // pko_Latn_KE - 0x706C504C4C61746Ellu, // pl_Latn_PL - 0xC98F49544C61746Ellu, // pms_Latn_IT - 0xCDAF47524772656Bllu, // pnt_Grek_GR - 0xB5CF464D4C61746Ellu, // pon_Latn_FM - 0x822F504B4B686172llu, // pra_Khar_PK - 0x8E2F495241726162llu, // prd_Arab_IR - 0x7073414641726162llu, // ps_Arab_AF - 0x707442524C61746Ellu, // pt_Latn_BR - 0xD28F47414C61746Ellu, // puu_Latn_GA - 0x717550454C61746Ellu, // qu_Latn_PE - 0x8A9047544C61746Ellu, // quc_Latn_GT - 0x9A9045434C61746Ellu, // qug_Latn_EC - 0xA411494E44657661llu, // raj_Deva_IN - 0x945152454C61746Ellu, // rcf_Latn_RE - 0xA49149444C61746Ellu, // rej_Latn_ID - 0xB4D149544C61746Ellu, // rgn_Latn_IT - 0x8111494E4C61746Ellu, // ria_Latn_IN - 0x95114D4154666E67llu, // rif_Tfng_MA - 0xC9314E5044657661llu, // rjs_Deva_NP - 0xCD51424442656E67llu, // rkt_Beng_BD - 0x726D43484C61746Ellu, // rm_Latn_CH - 0x959146494C61746Ellu, // rmf_Latn_FI - 0xB99143484C61746Ellu, // rmo_Latn_CH - 0xCD91495241726162llu, // rmt_Arab_IR - 0xD19153454C61746Ellu, // rmu_Latn_SE - 0x726E42494C61746Ellu, // rn_Latn_BI - 0x99B14D5A4C61746Ellu, // rng_Latn_MZ - 0x726F524F4C61746Ellu, // ro_Latn_RO - 0x85D149444C61746Ellu, // rob_Latn_ID - 0x95D1545A4C61746Ellu, // rof_Latn_TZ - 0xB271464A4C61746Ellu, // rtm_Latn_FJ - 0x727552554379726Cllu, // ru_Cyrl_RU - 0x929155414379726Cllu, // rue_Cyrl_UA - 0x9A9153424C61746Ellu, // rug_Latn_SB - 0x727752574C61746Ellu, // rw_Latn_RW - 0xAAD1545A4C61746Ellu, // rwk_Latn_TZ - 0xD3114A504B616E61llu, // ryu_Kana_JP - 0x7361494E44657661llu, // sa_Deva_IN - 0x941247484C61746Ellu, // saf_Latn_GH - 0x9C1252554379726Cllu, // sah_Cyrl_RU - 0xC0124B454C61746Ellu, // saq_Latn_KE - 0xC81249444C61746Ellu, // sas_Latn_ID - 0xCC12494E4C61746Ellu, // sat_Latn_IN - 0xE412494E53617572llu, // saz_Saur_IN - 0xBC32545A4C61746Ellu, // sbp_Latn_TZ - 0x736349544C61746Ellu, // sc_Latn_IT - 0xA852494E44657661llu, // sck_Deva_IN - 0xB45249544C61746Ellu, // scn_Latn_IT - 0xB85247424C61746Ellu, // sco_Latn_GB - 0xC85243414C61746Ellu, // scs_Latn_CA - 0x7364504B41726162llu, // sd_Arab_PK - 0x7364494E44657661llu, // sd_Deva_IN - 0x7364494E4B686F6Allu, // sd_Khoj_IN - 0x7364494E53696E64llu, // sd_Sind_IN - 0x887249544C61746Ellu, // sdc_Latn_IT - 0x9C72495241726162llu, // sdh_Arab_IR - 0x73654E4F4C61746Ellu, // se_Latn_NO - 0x949243494C61746Ellu, // sef_Latn_CI - 0x9C924D5A4C61746Ellu, // seh_Latn_MZ - 0xA0924D584C61746Ellu, // sei_Latn_MX - 0xC8924D4C4C61746Ellu, // ses_Latn_ML - 0x736743464C61746Ellu, // sg_Latn_CF - 0x80D249454F67616Dllu, // sga_Ogam_IE - 0xC8D24C544C61746Ellu, // sgs_Latn_LT - 0xA0F24D4154666E67llu, // shi_Tfng_MA - 0xB4F24D4D4D796D72llu, // shn_Mymr_MM - 0x73694C4B53696E68llu, // si_Sinh_LK - 0x8D1245544C61746Ellu, // sid_Latn_ET - 0x736B534B4C61746Ellu, // sk_Latn_SK - 0xC552504B41726162llu, // skr_Arab_PK - 0x736C53494C61746Ellu, // sl_Latn_SI - 0xA172504C4C61746Ellu, // sli_Latn_PL - 0xE17249444C61746Ellu, // sly_Latn_ID - 0x736D57534C61746Ellu, // sm_Latn_WS - 0x819253454C61746Ellu, // sma_Latn_SE - 0xA59253454C61746Ellu, // smj_Latn_SE - 0xB59246494C61746Ellu, // smn_Latn_FI - 0xBD92494C53616D72llu, // smp_Samr_IL - 0xC99246494C61746Ellu, // sms_Latn_FI - 0x736E5A574C61746Ellu, // sn_Latn_ZW - 0xA9B24D4C4C61746Ellu, // snk_Latn_ML - 0x736F534F4C61746Ellu, // so_Latn_SO - 0xD1D2544854686169llu, // sou_Thai_TH - 0x7371414C4C61746Ellu, // sq_Latn_AL - 0x737252534379726Cllu, // sr_Cyrl_RS - 0x737252534C61746Ellu, // sr_Latn_RS - 0x8632494E536F7261llu, // srb_Sora_IN - 0xB63253524C61746Ellu, // srn_Latn_SR - 0xC632534E4C61746Ellu, // srr_Latn_SN - 0xDE32494E44657661llu, // srx_Deva_IN - 0x73735A414C61746Ellu, // ss_Latn_ZA - 0xE25245524C61746Ellu, // ssy_Latn_ER - 0x73745A414C61746Ellu, // st_Latn_ZA - 0xC27244454C61746Ellu, // stq_Latn_DE - 0x737549444C61746Ellu, // su_Latn_ID - 0xAA92545A4C61746Ellu, // suk_Latn_TZ - 0xCA92474E4C61746Ellu, // sus_Latn_GN - 0x737653454C61746Ellu, // sv_Latn_SE - 0x7377545A4C61746Ellu, // sw_Latn_TZ - 0x86D2595441726162llu, // swb_Arab_YT - 0x8AD243444C61746Ellu, // swc_Latn_CD - 0x9AD244454C61746Ellu, // swg_Latn_DE - 0xD6D2494E44657661llu, // swv_Deva_IN - 0xB6F249444C61746Ellu, // sxn_Latn_ID - 0xAF12424442656E67llu, // syl_Beng_BD - 0xC712495153797263llu, // syr_Syrc_IQ - 0xAF32504C4C61746Ellu, // szl_Latn_PL - 0x7461494E54616D6Cllu, // ta_Taml_IN - 0xA4134E5044657661llu, // taj_Deva_NP - 0xD83350484C61746Ellu, // tbw_Latn_PH - 0xE053494E4B6E6461llu, // tcy_Knda_IN - 0x8C73434E54616C65llu, // tdd_Tale_CN - 0x98734E5044657661llu, // tdg_Deva_NP - 0x9C734E5044657661llu, // tdh_Deva_NP - 0x7465494E54656C75llu, // te_Telu_IN - 0xB093534C4C61746Ellu, // tem_Latn_SL - 0xB89355474C61746Ellu, // teo_Latn_UG - 0xCC93544C4C61746Ellu, // tet_Latn_TL - 0x7467504B41726162llu, // tg_Arab_PK - 0x7467544A4379726Cllu, // tg_Cyrl_TJ - 0x7468544854686169llu, // th_Thai_TH - 0xACF34E5044657661llu, // thl_Deva_NP - 0xC0F34E5044657661llu, // thq_Deva_NP - 0xC4F34E5044657661llu, // thr_Deva_NP - 0x7469455445746869llu, // ti_Ethi_ET - 0x9913455245746869llu, // tig_Ethi_ER - 0xD5134E474C61746Ellu, // tiv_Latn_NG - 0x746B544D4C61746Ellu, // tk_Latn_TM - 0xAD53544B4C61746Ellu, // tkl_Latn_TK - 0xC553415A4C61746Ellu, // tkr_Latn_AZ - 0xCD534E5044657661llu, // tkt_Deva_NP - 0x746C50484C61746Ellu, // tl_Latn_PH - 0xE173415A4C61746Ellu, // tly_Latn_AZ - 0x9D934E454C61746Ellu, // tmh_Latn_NE - 0x746E5A414C61746Ellu, // tn_Latn_ZA - 0x746F544F4C61746Ellu, // to_Latn_TO - 0x99D34D574C61746Ellu, // tog_Latn_MW - 0xA1F350474C61746Ellu, // tpi_Latn_PG - 0x747254524C61746Ellu, // tr_Latn_TR - 0xD23354524C61746Ellu, // tru_Latn_TR - 0xD63354574C61746Ellu, // trv_Latn_TW - 0x74735A414C61746Ellu, // ts_Latn_ZA - 0x8E5347524772656Bllu, // tsd_Grek_GR - 0x96534E5044657661llu, // tsf_Deva_NP - 0x9A5350484C61746Ellu, // tsg_Latn_PH - 0xA653425454696274llu, // tsj_Tibt_BT - 0x747452554379726Cllu, // tt_Cyrl_RU - 0xA67355474C61746Ellu, // ttj_Latn_UG - 0xCA73544854686169llu, // tts_Thai_TH - 0xCE73415A4C61746Ellu, // ttt_Latn_AZ - 0xB2934D574C61746Ellu, // tum_Latn_MW - 0xAEB354564C61746Ellu, // tvl_Latn_TV - 0xC2D34E454C61746Ellu, // twq_Latn_NE - 0x9AF3434E54616E67llu, // txg_Tang_CN - 0x747950464C61746Ellu, // ty_Latn_PF - 0xD71352554379726Cllu, // tyv_Cyrl_RU - 0xB3334D414C61746Ellu, // tzm_Latn_MA - 0xB07452554379726Cllu, // udm_Cyrl_RU - 0x7567434E41726162llu, // ug_Arab_CN - 0x75674B5A4379726Cllu, // ug_Cyrl_KZ - 0x80D4535955676172llu, // uga_Ugar_SY - 0x756B55414379726Cllu, // uk_Cyrl_UA - 0xA174464D4C61746Ellu, // uli_Latn_FM - 0x8594414F4C61746Ellu, // umb_Latn_AO - 0xC5B4494E42656E67llu, // unr_Beng_IN - 0xC5B44E5044657661llu, // unr_Deva_NP - 0xDDB4494E42656E67llu, // unx_Beng_IN - 0x7572504B41726162llu, // ur_Arab_PK - 0x757A414641726162llu, // uz_Arab_AF - 0x757A555A4C61746Ellu, // uz_Latn_UZ - 0xA0154C5256616969llu, // vai_Vaii_LR - 0x76655A414C61746Ellu, // ve_Latn_ZA - 0x889549544C61746Ellu, // vec_Latn_IT - 0xBC9552554C61746Ellu, // vep_Latn_RU - 0x7669564E4C61746Ellu, // vi_Latn_VN - 0x891553584C61746Ellu, // vic_Latn_SX - 0xC97542454C61746Ellu, // vls_Latn_BE - 0x959544454C61746Ellu, // vmf_Latn_DE - 0xD9954D5A4C61746Ellu, // vmw_Latn_MZ - 0xCDD552554C61746Ellu, // vot_Latn_RU - 0xBA3545454C61746Ellu, // vro_Latn_EE - 0xB695545A4C61746Ellu, // vun_Latn_TZ - 0x776142454C61746Ellu, // wa_Latn_BE - 0x901643484C61746Ellu, // wae_Latn_CH - 0xAC16455445746869llu, // wal_Ethi_ET - 0xC41650484C61746Ellu, // war_Latn_PH - 0xBC3641554C61746Ellu, // wbp_Latn_AU - 0xC036494E54656C75llu, // wbq_Telu_IN - 0xC436494E44657661llu, // wbr_Deva_IN - 0xC97657464C61746Ellu, // wls_Latn_WF - 0xA1B64B4D41726162llu, // wni_Arab_KM - 0x776F534E4C61746Ellu, // wo_Latn_SN - 0xB276494E44657661llu, // wtm_Deva_IN - 0xD296434E48616E73llu, // wuu_Hans_CN - 0xD41742524C61746Ellu, // xav_Latn_BR - 0xC457545243617269llu, // xcr_Cari_TR - 0x78685A414C61746Ellu, // xh_Latn_ZA - 0x897754524C796369llu, // xlc_Lyci_TR - 0x8D7754524C796469llu, // xld_Lydi_TR - 0x9597474547656F72llu, // xmf_Geor_GE - 0xB597434E4D616E69llu, // xmn_Mani_CN - 0xC59753444D657263llu, // xmr_Merc_SD - 0x81B753414E617262llu, // xna_Narb_SA - 0xC5B7494E44657661llu, // xnr_Deva_IN - 0x99D755474C61746Ellu, // xog_Latn_UG - 0xC5F7495250727469llu, // xpr_Prti_IR - 0x8257594553617262llu, // xsa_Sarb_YE - 0xC6574E5044657661llu, // xsr_Deva_NP - 0xB8184D5A4C61746Ellu, // yao_Latn_MZ - 0xBC18464D4C61746Ellu, // yap_Latn_FM - 0xD418434D4C61746Ellu, // yav_Latn_CM - 0x8438434D4C61746Ellu, // ybb_Latn_CM - 0x796F4E474C61746Ellu, // yo_Latn_NG - 0xAE3842524C61746Ellu, // yrl_Latn_BR - 0x82984D584C61746Ellu, // yua_Latn_MX - 0x9298434E48616E73llu, // yue_Hans_CN - 0x9298484B48616E74llu, // yue_Hant_HK - 0x7A61434E4C61746Ellu, // za_Latn_CN - 0x981953444C61746Ellu, // zag_Latn_SD - 0xA4794B4D41726162llu, // zdj_Arab_KM - 0x80994E4C4C61746Ellu, // zea_Latn_NL - 0x9CD94D4154666E67llu, // zgh_Tfng_MA - 0x7A685457426F706Fllu, // zh_Bopo_TW - 0x7A68545748616E62llu, // zh_Hanb_TW - 0x7A68434E48616E73llu, // zh_Hans_CN - 0x7A68545748616E74llu, // zh_Hant_TW - 0xB17954474C61746Ellu, // zlm_Latn_TG - 0xA1994D594C61746Ellu, // zmi_Latn_MY - 0x7A755A414C61746Ellu, // zu_Latn_ZA - 0x833954524C61746Ellu, // zza_Latn_TR + 0x616145544C61746ELLU, // aa_Latn_ET + 0x616247454379726CLLU, // ab_Cyrl_GE + 0xC42047484C61746ELLU, // abr_Latn_GH + 0x904049444C61746ELLU, // ace_Latn_ID + 0x9C4055474C61746ELLU, // ach_Latn_UG + 0x806047484C61746ELLU, // ada_Latn_GH + 0xE06052554379726CLLU, // ady_Cyrl_RU + 0x6165495241767374LLU, // ae_Avst_IR + 0x8480544E41726162LLU, // aeb_Arab_TN + 0x61665A414C61746ELLU, // af_Latn_ZA + 0xC0C0434D4C61746ELLU, // agq_Latn_CM + 0xB8E0494E41686F6DLLU, // aho_Ahom_IN + 0x616B47484C61746ELLU, // ak_Latn_GH + 0xA940495158737578LLU, // akk_Xsux_IQ + 0xB560584B4C61746ELLU, // aln_Latn_XK + 0xCD6052554379726CLLU, // alt_Cyrl_RU + 0x616D455445746869LLU, // am_Ethi_ET + 0xB9804E474C61746ELLU, // amo_Latn_NG + 0xE5C049444C61746ELLU, // aoz_Latn_ID + 0x8DE0544741726162LLU, // apd_Arab_TG + 0x6172454741726162LLU, // ar_Arab_EG + 0x8A20495241726D69LLU, // arc_Armi_IR + 0x8A204A4F4E626174LLU, // arc_Nbat_JO + 0x8A20535950616C6DLLU, // arc_Palm_SY + 0xB620434C4C61746ELLU, // arn_Latn_CL + 0xBA20424F4C61746ELLU, // aro_Latn_BO + 0xC220445A41726162LLU, // arq_Arab_DZ + 0xE2204D4141726162LLU, // ary_Arab_MA + 0xE620454741726162LLU, // arz_Arab_EG + 0x6173494E42656E67LLU, // as_Beng_IN + 0x8240545A4C61746ELLU, // asa_Latn_TZ + 0x9240555353676E77LLU, // ase_Sgnw_US + 0xCE4045534C61746ELLU, // ast_Latn_ES + 0xA66043414C61746ELLU, // atj_Latn_CA + 0x617652554379726CLLU, // av_Cyrl_RU + 0x82C0494E44657661LLU, // awa_Deva_IN + 0x6179424F4C61746ELLU, // ay_Latn_BO + 0x617A495241726162LLU, // az_Arab_IR + 0x617A415A4C61746ELLU, // az_Latn_AZ + 0x626152554379726CLLU, // ba_Cyrl_RU + 0xAC01504B41726162LLU, // bal_Arab_PK + 0xB40149444C61746ELLU, // ban_Latn_ID + 0xBC014E5044657661LLU, // bap_Deva_NP + 0xC40141544C61746ELLU, // bar_Latn_AT + 0xC801434D4C61746ELLU, // bas_Latn_CM + 0xDC01434D42616D75LLU, // bax_Bamu_CM + 0x882149444C61746ELLU, // bbc_Latn_ID + 0xA421434D4C61746ELLU, // bbj_Latn_CM + 0xA04143494C61746ELLU, // bci_Latn_CI + 0x626542594379726CLLU, // be_Cyrl_BY + 0xA481534441726162LLU, // bej_Arab_SD + 0xB0815A4D4C61746ELLU, // bem_Latn_ZM + 0xD88149444C61746ELLU, // bew_Latn_ID + 0xE481545A4C61746ELLU, // bez_Latn_TZ + 0x8CA1434D4C61746ELLU, // bfd_Latn_CM + 0xC0A1494E54616D6CLLU, // bfq_Taml_IN + 0xCCA1504B41726162LLU, // bft_Arab_PK + 0xE0A1494E44657661LLU, // bfy_Deva_IN + 0x626742474379726CLLU, // bg_Cyrl_BG + 0x88C1494E44657661LLU, // bgc_Deva_IN + 0xB4C1504B41726162LLU, // bgn_Arab_PK + 0xDCC154524772656BLLU, // bgx_Grek_TR + 0x84E1494E44657661LLU, // bhb_Deva_IN + 0xA0E1494E44657661LLU, // bhi_Deva_IN + 0xA8E150484C61746ELLU, // bhk_Latn_PH + 0xB8E1494E44657661LLU, // bho_Deva_IN + 0x626956554C61746ELLU, // bi_Latn_VU + 0xA90150484C61746ELLU, // bik_Latn_PH + 0xB5014E474C61746ELLU, // bin_Latn_NG + 0xA521494E44657661LLU, // bjj_Deva_IN + 0xB52149444C61746ELLU, // bjn_Latn_ID + 0xB141434D4C61746ELLU, // bkm_Latn_CM + 0xD14150484C61746ELLU, // bku_Latn_PH + 0xCD61564E54617674LLU, // blt_Tavt_VN + 0x626D4D4C4C61746ELLU, // bm_Latn_ML + 0xC1814D4C4C61746ELLU, // bmq_Latn_ML + 0x626E424442656E67LLU, // bn_Beng_BD + 0x626F434E54696274LLU, // bo_Tibt_CN + 0xE1E1494E42656E67LLU, // bpy_Beng_IN + 0xA201495241726162LLU, // bqi_Arab_IR + 0xD60143494C61746ELLU, // bqv_Latn_CI + 0x627246524C61746ELLU, // br_Latn_FR + 0x8221494E44657661LLU, // bra_Deva_IN + 0x9E21504B41726162LLU, // brh_Arab_PK + 0xDE21494E44657661LLU, // brx_Deva_IN + 0x627342414C61746ELLU, // bs_Latn_BA + 0xC2414C5242617373LLU, // bsq_Bass_LR + 0xCA41434D4C61746ELLU, // bss_Latn_CM + 0xBA6150484C61746ELLU, // bto_Latn_PH + 0xD661504B44657661LLU, // btv_Deva_PK + 0x828152554379726CLLU, // bua_Cyrl_RU + 0x8A8159544C61746ELLU, // buc_Latn_YT + 0x9A8149444C61746ELLU, // bug_Latn_ID + 0xB281434D4C61746ELLU, // bum_Latn_CM + 0x86A147514C61746ELLU, // bvb_Latn_GQ + 0xB701455245746869LLU, // byn_Ethi_ER + 0xD701434D4C61746ELLU, // byv_Latn_CM + 0x93214D4C4C61746ELLU, // bze_Latn_ML + 0x636145534C61746ELLU, // ca_Latn_ES + 0x9C424E474C61746ELLU, // cch_Latn_NG + 0xBC42494E42656E67LLU, // ccp_Beng_IN + 0xBC42424443616B6DLLU, // ccp_Cakm_BD + 0x636552554379726CLLU, // ce_Cyrl_RU + 0x848250484C61746ELLU, // ceb_Latn_PH + 0x98C255474C61746ELLU, // cgg_Latn_UG + 0x636847554C61746ELLU, // ch_Latn_GU + 0xA8E2464D4C61746ELLU, // chk_Latn_FM + 0xB0E252554379726CLLU, // chm_Cyrl_RU + 0xB8E255534C61746ELLU, // cho_Latn_US + 0xBCE243414C61746ELLU, // chp_Latn_CA + 0xC4E2555343686572LLU, // chr_Cher_US + 0x81224B4841726162LLU, // cja_Arab_KH + 0xB122564E4368616DLLU, // cjm_Cham_VN + 0x8542495141726162LLU, // ckb_Arab_IQ + 0x636F46524C61746ELLU, // co_Latn_FR + 0xBDC24547436F7074LLU, // cop_Copt_EG + 0xC9E250484C61746ELLU, // cps_Latn_PH + 0x6372434143616E73LLU, // cr_Cans_CA + 0xA622434143616E73LLU, // crj_Cans_CA + 0xAA22434143616E73LLU, // crk_Cans_CA + 0xAE22434143616E73LLU, // crl_Cans_CA + 0xB222434143616E73LLU, // crm_Cans_CA + 0xCA2253434C61746ELLU, // crs_Latn_SC + 0x6373435A4C61746ELLU, // cs_Latn_CZ + 0x8642504C4C61746ELLU, // csb_Latn_PL + 0xDA42434143616E73LLU, // csw_Cans_CA + 0x8E624D4D50617563LLU, // ctd_Pauc_MM + 0x637552554379726CLLU, // cu_Cyrl_RU + 0x63754247476C6167LLU, // cu_Glag_BG + 0x637652554379726CLLU, // cv_Cyrl_RU + 0x637947424C61746ELLU, // cy_Latn_GB + 0x6461444B4C61746ELLU, // da_Latn_DK + 0xA80355534C61746ELLU, // dak_Latn_US + 0xC40352554379726CLLU, // dar_Cyrl_RU + 0xD4034B454C61746ELLU, // dav_Latn_KE + 0x8843494E41726162LLU, // dcc_Arab_IN + 0x646544454C61746ELLU, // de_Latn_DE + 0xB48343414C61746ELLU, // den_Latn_CA + 0xC4C343414C61746ELLU, // dgr_Latn_CA + 0x91234E454C61746ELLU, // dje_Latn_NE + 0xA5A343494C61746ELLU, // dnj_Latn_CI + 0xA1C3494E41726162LLU, // doi_Arab_IN + 0x864344454C61746ELLU, // dsb_Latn_DE + 0xB2634D4C4C61746ELLU, // dtm_Latn_ML + 0xBE634D594C61746ELLU, // dtp_Latn_MY + 0xE2634E5044657661LLU, // dty_Deva_NP + 0x8283434D4C61746ELLU, // dua_Latn_CM + 0x64764D5654686161LLU, // dv_Thaa_MV + 0xBB03534E4C61746ELLU, // dyo_Latn_SN + 0xD30342464C61746ELLU, // dyu_Latn_BF + 0x647A425454696274LLU, // dz_Tibt_BT + 0xD0244B454C61746ELLU, // ebu_Latn_KE + 0x656547484C61746ELLU, // ee_Latn_GH + 0xA0A44E474C61746ELLU, // efi_Latn_NG + 0xACC449544C61746ELLU, // egl_Latn_IT + 0xE0C4454745677970LLU, // egy_Egyp_EG + 0xE1444D4D4B616C69LLU, // eky_Kali_MM + 0x656C47524772656BLLU, // el_Grek_GR + 0x656E47424C61746ELLU, // en_Latn_GB + 0x656E55534C61746ELLU, // en_Latn_US + 0x656E474253686177LLU, // en_Shaw_GB + 0x657345534C61746ELLU, // es_Latn_ES + 0x65734D584C61746ELLU, // es_Latn_MX + 0x657355534C61746ELLU, // es_Latn_US + 0xD24455534C61746ELLU, // esu_Latn_US + 0x657445454C61746ELLU, // et_Latn_EE + 0xCE6449544974616CLLU, // ett_Ital_IT + 0x657545534C61746ELLU, // eu_Latn_ES + 0xBAC4434D4C61746ELLU, // ewo_Latn_CM + 0xCEE445534C61746ELLU, // ext_Latn_ES + 0x6661495241726162LLU, // fa_Arab_IR + 0xB40547514C61746ELLU, // fan_Latn_GQ + 0x6666474E41646C6DLLU, // ff_Adlm_GN + 0x6666534E4C61746ELLU, // ff_Latn_SN + 0xB0A54D4C4C61746ELLU, // ffm_Latn_ML + 0x666946494C61746ELLU, // fi_Latn_FI + 0x8105534441726162LLU, // fia_Arab_SD + 0xAD0550484C61746ELLU, // fil_Latn_PH + 0xCD0553454C61746ELLU, // fit_Latn_SE + 0x666A464A4C61746ELLU, // fj_Latn_FJ + 0x666F464F4C61746ELLU, // fo_Latn_FO + 0xB5C5424A4C61746ELLU, // fon_Latn_BJ + 0x667246524C61746ELLU, // fr_Latn_FR + 0x8A2555534C61746ELLU, // frc_Latn_US + 0xBE2546524C61746ELLU, // frp_Latn_FR + 0xC62544454C61746ELLU, // frr_Latn_DE + 0xCA2544454C61746ELLU, // frs_Latn_DE + 0x8685434D41726162LLU, // fub_Arab_CM + 0x8E8557464C61746ELLU, // fud_Latn_WF + 0x9685474E4C61746ELLU, // fuf_Latn_GN + 0xC2854E454C61746ELLU, // fuq_Latn_NE + 0xC68549544C61746ELLU, // fur_Latn_IT + 0xD6854E474C61746ELLU, // fuv_Latn_NG + 0xC6A553444C61746ELLU, // fvr_Latn_SD + 0x66794E4C4C61746ELLU, // fy_Latn_NL + 0x676149454C61746ELLU, // ga_Latn_IE + 0x800647484C61746ELLU, // gaa_Latn_GH + 0x98064D444C61746ELLU, // gag_Latn_MD + 0xB406434E48616E73LLU, // gan_Hans_CN + 0xE00649444C61746ELLU, // gay_Latn_ID + 0xB026494E44657661LLU, // gbm_Deva_IN + 0xE426495241726162LLU, // gbz_Arab_IR + 0xC44647464C61746ELLU, // gcr_Latn_GF + 0x676447424C61746ELLU, // gd_Latn_GB + 0xE486455445746869LLU, // gez_Ethi_ET + 0xB4C64E5044657661LLU, // ggn_Deva_NP + 0xAD064B494C61746ELLU, // gil_Latn_KI + 0xA926504B41726162LLU, // gjk_Arab_PK + 0xD126504B41726162LLU, // gju_Arab_PK + 0x676C45534C61746ELLU, // gl_Latn_ES + 0xA966495241726162LLU, // glk_Arab_IR + 0x676E50594C61746ELLU, // gn_Latn_PY + 0xB1C6494E44657661LLU, // gom_Deva_IN + 0xB5C6494E54656C75LLU, // gon_Telu_IN + 0xC5C649444C61746ELLU, // gor_Latn_ID + 0xC9C64E4C4C61746ELLU, // gos_Latn_NL + 0xCDC65541476F7468LLU, // got_Goth_UA + 0x8A26435943707274LLU, // grc_Cprt_CY + 0x8A2647524C696E62LLU, // grc_Linb_GR + 0xCE26494E42656E67LLU, // grt_Beng_IN + 0xDA4643484C61746ELLU, // gsw_Latn_CH + 0x6775494E47756A72LLU, // gu_Gujr_IN + 0x868642524C61746ELLU, // gub_Latn_BR + 0x8A86434F4C61746ELLU, // guc_Latn_CO + 0xC68647484C61746ELLU, // gur_Latn_GH + 0xE6864B454C61746ELLU, // guz_Latn_KE + 0x6776494D4C61746ELLU, // gv_Latn_IM + 0xC6A64E5044657661LLU, // gvr_Deva_NP + 0xA2C643414C61746ELLU, // gwi_Latn_CA + 0x68614E474C61746ELLU, // ha_Latn_NG + 0xA807434E48616E73LLU, // hak_Hans_CN + 0xD80755534C61746ELLU, // haw_Latn_US + 0xE407414641726162LLU, // haz_Arab_AF + 0x6865494C48656272LLU, // he_Hebr_IL + 0x6869494E44657661LLU, // hi_Deva_IN + 0x9507464A4C61746ELLU, // hif_Latn_FJ + 0xAD0750484C61746ELLU, // hil_Latn_PH + 0xD1675452486C7577LLU, // hlu_Hluw_TR + 0x8D87434E506C7264LLU, // hmd_Plrd_CN + 0x8DA7504B41726162LLU, // hnd_Arab_PK + 0x91A7494E44657661LLU, // hne_Deva_IN + 0xA5A74C41486D6E67LLU, // hnj_Hmng_LA + 0xB5A750484C61746ELLU, // hnn_Latn_PH + 0xB9A7504B41726162LLU, // hno_Arab_PK + 0x686F50474C61746ELLU, // ho_Latn_PG + 0x89C7494E44657661LLU, // hoc_Deva_IN + 0xA5C7494E44657661LLU, // hoj_Deva_IN + 0x687248524C61746ELLU, // hr_Latn_HR + 0x864744454C61746ELLU, // hsb_Latn_DE + 0xB647434E48616E73LLU, // hsn_Hans_CN + 0x687448544C61746ELLU, // ht_Latn_HT + 0x687548554C61746ELLU, // hu_Latn_HU + 0x6879414D41726D6ELLU, // hy_Armn_AM + 0x687A4E414C61746ELLU, // hz_Latn_NA + 0x696146524C61746ELLU, // ia_Latn_FR + 0x80284D594C61746ELLU, // iba_Latn_MY + 0x84284E474C61746ELLU, // ibb_Latn_NG + 0x696449444C61746ELLU, // id_Latn_ID + 0x69674E474C61746ELLU, // ig_Latn_NG + 0x6969434E59696969LLU, // ii_Yiii_CN + 0x696B55534C61746ELLU, // ik_Latn_US + 0xCD4843414C61746ELLU, // ikt_Latn_CA + 0xB96850484C61746ELLU, // ilo_Latn_PH + 0x696E49444C61746ELLU, // in_Latn_ID + 0x9DA852554379726CLLU, // inh_Cyrl_RU + 0x697349534C61746ELLU, // is_Latn_IS + 0x697449544C61746ELLU, // it_Latn_IT + 0x6975434143616E73LLU, // iu_Cans_CA + 0x6977494C48656272LLU, // iw_Hebr_IL + 0x9F2852554C61746ELLU, // izh_Latn_RU + 0x6A614A504A70616ELLU, // ja_Jpan_JP + 0xB0094A4D4C61746ELLU, // jam_Latn_JM + 0xB8C9434D4C61746ELLU, // jgo_Latn_CM + 0x8989545A4C61746ELLU, // jmc_Latn_TZ + 0xAD894E5044657661LLU, // jml_Deva_NP + 0xCE89444B4C61746ELLU, // jut_Latn_DK + 0x6A7649444C61746ELLU, // jv_Latn_ID + 0x6A7749444C61746ELLU, // jw_Latn_ID + 0x6B61474547656F72LLU, // ka_Geor_GE + 0x800A555A4379726CLLU, // kaa_Cyrl_UZ + 0x840A445A4C61746ELLU, // kab_Latn_DZ + 0x880A4D4D4C61746ELLU, // kac_Latn_MM + 0xA40A4E474C61746ELLU, // kaj_Latn_NG + 0xB00A4B454C61746ELLU, // kam_Latn_KE + 0xB80A4D4C4C61746ELLU, // kao_Latn_ML + 0x8C2A52554379726CLLU, // kbd_Cyrl_RU + 0xE02A4E4541726162LLU, // kby_Arab_NE + 0x984A4E474C61746ELLU, // kcg_Latn_NG + 0xA84A5A574C61746ELLU, // kck_Latn_ZW + 0x906A545A4C61746ELLU, // kde_Latn_TZ + 0x9C6A544741726162LLU, // kdh_Arab_TG + 0xCC6A544854686169LLU, // kdt_Thai_TH + 0x808A43564C61746ELLU, // kea_Latn_CV + 0xB48A434D4C61746ELLU, // ken_Latn_CM + 0xB8AA43494C61746ELLU, // kfo_Latn_CI + 0xC4AA494E44657661LLU, // kfr_Deva_IN + 0xE0AA494E44657661LLU, // kfy_Deva_IN + 0x6B6743444C61746ELLU, // kg_Latn_CD + 0x90CA49444C61746ELLU, // kge_Latn_ID + 0xBCCA42524C61746ELLU, // kgp_Latn_BR + 0x80EA494E4C61746ELLU, // kha_Latn_IN + 0x84EA434E54616C75LLU, // khb_Talu_CN + 0xB4EA494E44657661LLU, // khn_Deva_IN + 0xC0EA4D4C4C61746ELLU, // khq_Latn_ML + 0xCCEA494E4D796D72LLU, // kht_Mymr_IN + 0xD8EA504B41726162LLU, // khw_Arab_PK + 0x6B694B454C61746ELLU, // ki_Latn_KE + 0xD10A54524C61746ELLU, // kiu_Latn_TR + 0x6B6A4E414C61746ELLU, // kj_Latn_NA + 0x992A4C414C616F6FLLU, // kjg_Laoo_LA + 0x6B6B434E41726162LLU, // kk_Arab_CN + 0x6B6B4B5A4379726CLLU, // kk_Cyrl_KZ + 0xA54A434D4C61746ELLU, // kkj_Latn_CM + 0x6B6C474C4C61746ELLU, // kl_Latn_GL + 0xB56A4B454C61746ELLU, // kln_Latn_KE + 0x6B6D4B484B686D72LLU, // km_Khmr_KH + 0x858A414F4C61746ELLU, // kmb_Latn_AO + 0x6B6E494E4B6E6461LLU, // kn_Knda_IN + 0x6B6F4B524B6F7265LLU, // ko_Kore_KR + 0xA1CA52554379726CLLU, // koi_Cyrl_RU + 0xA9CA494E44657661LLU, // kok_Deva_IN + 0xC9CA464D4C61746ELLU, // kos_Latn_FM + 0x91EA4C524C61746ELLU, // kpe_Latn_LR + 0x8A2A52554379726CLLU, // krc_Cyrl_RU + 0xA22A534C4C61746ELLU, // kri_Latn_SL + 0xA62A50484C61746ELLU, // krj_Latn_PH + 0xAE2A52554C61746ELLU, // krl_Latn_RU + 0xD22A494E44657661LLU, // kru_Deva_IN + 0x6B73494E41726162LLU, // ks_Arab_IN + 0x864A545A4C61746ELLU, // ksb_Latn_TZ + 0x964A434D4C61746ELLU, // ksf_Latn_CM + 0x9E4A44454C61746ELLU, // ksh_Latn_DE + 0x6B75495141726162LLU, // ku_Arab_IQ + 0x6B7554524C61746ELLU, // ku_Latn_TR + 0xB28A52554379726CLLU, // kum_Cyrl_RU + 0x6B7652554379726CLLU, // kv_Cyrl_RU + 0xC6AA49444C61746ELLU, // kvr_Latn_ID + 0xDEAA504B41726162LLU, // kvx_Arab_PK + 0x6B7747424C61746ELLU, // kw_Latn_GB + 0xB2EA544854686169LLU, // kxm_Thai_TH + 0xBEEA504B41726162LLU, // kxp_Arab_PK + 0x6B79434E41726162LLU, // ky_Arab_CN + 0x6B794B474379726CLLU, // ky_Cyrl_KG + 0x6B7954524C61746ELLU, // ky_Latn_TR + 0x6C6156414C61746ELLU, // la_Latn_VA + 0x840B47524C696E61LLU, // lab_Lina_GR + 0x8C0B494C48656272LLU, // lad_Hebr_IL + 0x980B545A4C61746ELLU, // lag_Latn_TZ + 0x9C0B504B41726162LLU, // lah_Arab_PK + 0xA40B55474C61746ELLU, // laj_Latn_UG + 0x6C624C554C61746ELLU, // lb_Latn_LU + 0x902B52554379726CLLU, // lbe_Cyrl_RU + 0xD82B49444C61746ELLU, // lbw_Latn_ID + 0xBC4B434E54686169LLU, // lcp_Thai_CN + 0xBC8B494E4C657063LLU, // lep_Lepc_IN + 0xE48B52554379726CLLU, // lez_Cyrl_RU + 0x6C6755474C61746ELLU, // lg_Latn_UG + 0x6C694E4C4C61746ELLU, // li_Latn_NL + 0x950B4E5044657661LLU, // lif_Deva_NP + 0x950B494E4C696D62LLU, // lif_Limb_IN + 0xA50B49544C61746ELLU, // lij_Latn_IT + 0xC90B434E4C697375LLU, // lis_Lisu_CN + 0xBD2B49444C61746ELLU, // ljp_Latn_ID + 0xA14B495241726162LLU, // lki_Arab_IR + 0xCD4B55534C61746ELLU, // lkt_Latn_US + 0xB58B494E54656C75LLU, // lmn_Telu_IN + 0xB98B49544C61746ELLU, // lmo_Latn_IT + 0x6C6E43444C61746ELLU, // ln_Latn_CD + 0x6C6F4C414C616F6FLLU, // lo_Laoo_LA + 0xADCB43444C61746ELLU, // lol_Latn_CD + 0xE5CB5A4D4C61746ELLU, // loz_Latn_ZM + 0x8A2B495241726162LLU, // lrc_Arab_IR + 0x6C744C544C61746ELLU, // lt_Latn_LT + 0x9A6B4C564C61746ELLU, // ltg_Latn_LV + 0x6C7543444C61746ELLU, // lu_Latn_CD + 0x828B43444C61746ELLU, // lua_Latn_CD + 0xBA8B4B454C61746ELLU, // luo_Latn_KE + 0xE28B4B454C61746ELLU, // luy_Latn_KE + 0xE68B495241726162LLU, // luz_Arab_IR + 0x6C764C564C61746ELLU, // lv_Latn_LV + 0xAECB544854686169LLU, // lwl_Thai_TH + 0x9F2B434E48616E73LLU, // lzh_Hans_CN + 0xE72B54524C61746ELLU, // lzz_Latn_TR + 0x8C0C49444C61746ELLU, // mad_Latn_ID + 0x940C434D4C61746ELLU, // maf_Latn_CM + 0x980C494E44657661LLU, // mag_Deva_IN + 0xA00C494E44657661LLU, // mai_Deva_IN + 0xA80C49444C61746ELLU, // mak_Latn_ID + 0xB40C474D4C61746ELLU, // man_Latn_GM + 0xB40C474E4E6B6F6FLLU, // man_Nkoo_GN + 0xC80C4B454C61746ELLU, // mas_Latn_KE + 0xE40C4D584C61746ELLU, // maz_Latn_MX + 0x946C52554379726CLLU, // mdf_Cyrl_RU + 0x9C6C50484C61746ELLU, // mdh_Latn_PH + 0xC46C49444C61746ELLU, // mdr_Latn_ID + 0xB48C534C4C61746ELLU, // men_Latn_SL + 0xC48C4B454C61746ELLU, // mer_Latn_KE + 0x80AC544841726162LLU, // mfa_Arab_TH + 0x90AC4D554C61746ELLU, // mfe_Latn_MU + 0x6D674D474C61746ELLU, // mg_Latn_MG + 0x9CCC4D5A4C61746ELLU, // mgh_Latn_MZ + 0xB8CC434D4C61746ELLU, // mgo_Latn_CM + 0xBCCC4E5044657661LLU, // mgp_Deva_NP + 0xE0CC545A4C61746ELLU, // mgy_Latn_TZ + 0x6D684D484C61746ELLU, // mh_Latn_MH + 0x6D694E5A4C61746ELLU, // mi_Latn_NZ + 0xB50C49444C61746ELLU, // min_Latn_ID + 0xC90C495148617472LLU, // mis_Hatr_IQ + 0x6D6B4D4B4379726CLLU, // mk_Cyrl_MK + 0x6D6C494E4D6C796DLLU, // ml_Mlym_IN + 0xC96C53444C61746ELLU, // mls_Latn_SD + 0x6D6E4D4E4379726CLLU, // mn_Cyrl_MN + 0x6D6E434E4D6F6E67LLU, // mn_Mong_CN + 0xA1AC494E42656E67LLU, // mni_Beng_IN + 0xD9AC4D4D4D796D72LLU, // mnw_Mymr_MM + 0x91CC43414C61746ELLU, // moe_Latn_CA + 0x9DCC43414C61746ELLU, // moh_Latn_CA + 0xC9CC42464C61746ELLU, // mos_Latn_BF + 0x6D72494E44657661LLU, // mr_Deva_IN + 0x8E2C4E5044657661LLU, // mrd_Deva_NP + 0xA62C52554379726CLLU, // mrj_Cyrl_RU + 0xBA2C42444D726F6FLLU, // mro_Mroo_BD + 0x6D734D594C61746ELLU, // ms_Latn_MY + 0x6D744D544C61746ELLU, // mt_Latn_MT + 0xC66C494E44657661LLU, // mtr_Deva_IN + 0x828C434D4C61746ELLU, // mua_Latn_CM + 0xCA8C55534C61746ELLU, // mus_Latn_US + 0xE2AC504B41726162LLU, // mvy_Arab_PK + 0xAACC4D4C4C61746ELLU, // mwk_Latn_ML + 0xC6CC494E44657661LLU, // mwr_Deva_IN + 0xD6CC49444C61746ELLU, // mwv_Latn_ID + 0x8AEC5A574C61746ELLU, // mxc_Latn_ZW + 0x6D794D4D4D796D72LLU, // my_Mymr_MM + 0xD70C52554379726CLLU, // myv_Cyrl_RU + 0xDF0C55474C61746ELLU, // myx_Latn_UG + 0xE70C49524D616E64LLU, // myz_Mand_IR + 0xB72C495241726162LLU, // mzn_Arab_IR + 0x6E614E524C61746ELLU, // na_Latn_NR + 0xB40D434E48616E73LLU, // nan_Hans_CN + 0xBC0D49544C61746ELLU, // nap_Latn_IT + 0xC00D4E414C61746ELLU, // naq_Latn_NA + 0x6E624E4F4C61746ELLU, // nb_Latn_NO + 0x9C4D4D584C61746ELLU, // nch_Latn_MX + 0x6E645A574C61746ELLU, // nd_Latn_ZW + 0x886D4D5A4C61746ELLU, // ndc_Latn_MZ + 0xC86D44454C61746ELLU, // nds_Latn_DE + 0x6E654E5044657661LLU, // ne_Deva_NP + 0xD88D4E5044657661LLU, // new_Deva_NP + 0x6E674E414C61746ELLU, // ng_Latn_NA + 0xACCD4D5A4C61746ELLU, // ngl_Latn_MZ + 0x90ED4D584C61746ELLU, // nhe_Latn_MX + 0xD8ED4D584C61746ELLU, // nhw_Latn_MX + 0xA50D49444C61746ELLU, // nij_Latn_ID + 0xD10D4E554C61746ELLU, // niu_Latn_NU + 0xB92D494E4C61746ELLU, // njo_Latn_IN + 0x6E6C4E4C4C61746ELLU, // nl_Latn_NL + 0x998D434D4C61746ELLU, // nmg_Latn_CM + 0x6E6E4E4F4C61746ELLU, // nn_Latn_NO + 0x9DAD434D4C61746ELLU, // nnh_Latn_CM + 0x6E6F4E4F4C61746ELLU, // no_Latn_NO + 0x8DCD54484C616E61LLU, // nod_Lana_TH + 0x91CD494E44657661LLU, // noe_Deva_IN + 0xB5CD534552756E72LLU, // non_Runr_SE + 0xBA0D474E4E6B6F6FLLU, // nqo_Nkoo_GN + 0x6E725A414C61746ELLU, // nr_Latn_ZA + 0xAA4D434143616E73LLU, // nsk_Cans_CA + 0xBA4D5A414C61746ELLU, // nso_Latn_ZA + 0xCA8D53534C61746ELLU, // nus_Latn_SS + 0x6E7655534C61746ELLU, // nv_Latn_US + 0xC2ED434E4C61746ELLU, // nxq_Latn_CN + 0x6E794D574C61746ELLU, // ny_Latn_MW + 0xB30D545A4C61746ELLU, // nym_Latn_TZ + 0xB70D55474C61746ELLU, // nyn_Latn_UG + 0xA32D47484C61746ELLU, // nzi_Latn_GH + 0x6F6346524C61746ELLU, // oc_Latn_FR + 0x6F6D45544C61746ELLU, // om_Latn_ET + 0x6F72494E4F727961LLU, // or_Orya_IN + 0x6F7347454379726CLLU, // os_Cyrl_GE + 0x824E55534F736765LLU, // osa_Osge_US + 0xAA6E4D4E4F726B68LLU, // otk_Orkh_MN + 0x7061504B41726162LLU, // pa_Arab_PK + 0x7061494E47757275LLU, // pa_Guru_IN + 0x980F50484C61746ELLU, // pag_Latn_PH + 0xAC0F495250686C69LLU, // pal_Phli_IR + 0xAC0F434E50686C70LLU, // pal_Phlp_CN + 0xB00F50484C61746ELLU, // pam_Latn_PH + 0xBC0F41574C61746ELLU, // pap_Latn_AW + 0xD00F50574C61746ELLU, // pau_Latn_PW + 0x8C4F46524C61746ELLU, // pcd_Latn_FR + 0xB04F4E474C61746ELLU, // pcm_Latn_NG + 0x886F55534C61746ELLU, // pdc_Latn_US + 0xCC6F43414C61746ELLU, // pdt_Latn_CA + 0xB88F49525870656FLLU, // peo_Xpeo_IR + 0xACAF44454C61746ELLU, // pfl_Latn_DE + 0xB4EF4C4250686E78LLU, // phn_Phnx_LB + 0x814F494E42726168LLU, // pka_Brah_IN + 0xB94F4B454C61746ELLU, // pko_Latn_KE + 0x706C504C4C61746ELLU, // pl_Latn_PL + 0xC98F49544C61746ELLU, // pms_Latn_IT + 0xCDAF47524772656BLLU, // pnt_Grek_GR + 0xB5CF464D4C61746ELLU, // pon_Latn_FM + 0x822F504B4B686172LLU, // pra_Khar_PK + 0x8E2F495241726162LLU, // prd_Arab_IR + 0x7073414641726162LLU, // ps_Arab_AF + 0x707442524C61746ELLU, // pt_Latn_BR + 0xD28F47414C61746ELLU, // puu_Latn_GA + 0x717550454C61746ELLU, // qu_Latn_PE + 0x8A9047544C61746ELLU, // quc_Latn_GT + 0x9A9045434C61746ELLU, // qug_Latn_EC + 0xA411494E44657661LLU, // raj_Deva_IN + 0x945152454C61746ELLU, // rcf_Latn_RE + 0xA49149444C61746ELLU, // rej_Latn_ID + 0xB4D149544C61746ELLU, // rgn_Latn_IT + 0x8111494E4C61746ELLU, // ria_Latn_IN + 0x95114D4154666E67LLU, // rif_Tfng_MA + 0xC9314E5044657661LLU, // rjs_Deva_NP + 0xCD51424442656E67LLU, // rkt_Beng_BD + 0x726D43484C61746ELLU, // rm_Latn_CH + 0x959146494C61746ELLU, // rmf_Latn_FI + 0xB99143484C61746ELLU, // rmo_Latn_CH + 0xCD91495241726162LLU, // rmt_Arab_IR + 0xD19153454C61746ELLU, // rmu_Latn_SE + 0x726E42494C61746ELLU, // rn_Latn_BI + 0x99B14D5A4C61746ELLU, // rng_Latn_MZ + 0x726F524F4C61746ELLU, // ro_Latn_RO + 0x85D149444C61746ELLU, // rob_Latn_ID + 0x95D1545A4C61746ELLU, // rof_Latn_TZ + 0xB271464A4C61746ELLU, // rtm_Latn_FJ + 0x727552554379726CLLU, // ru_Cyrl_RU + 0x929155414379726CLLU, // rue_Cyrl_UA + 0x9A9153424C61746ELLU, // rug_Latn_SB + 0x727752574C61746ELLU, // rw_Latn_RW + 0xAAD1545A4C61746ELLU, // rwk_Latn_TZ + 0xD3114A504B616E61LLU, // ryu_Kana_JP + 0x7361494E44657661LLU, // sa_Deva_IN + 0x941247484C61746ELLU, // saf_Latn_GH + 0x9C1252554379726CLLU, // sah_Cyrl_RU + 0xC0124B454C61746ELLU, // saq_Latn_KE + 0xC81249444C61746ELLU, // sas_Latn_ID + 0xCC12494E4C61746ELLU, // sat_Latn_IN + 0xE412494E53617572LLU, // saz_Saur_IN + 0xBC32545A4C61746ELLU, // sbp_Latn_TZ + 0x736349544C61746ELLU, // sc_Latn_IT + 0xA852494E44657661LLU, // sck_Deva_IN + 0xB45249544C61746ELLU, // scn_Latn_IT + 0xB85247424C61746ELLU, // sco_Latn_GB + 0xC85243414C61746ELLU, // scs_Latn_CA + 0x7364504B41726162LLU, // sd_Arab_PK + 0x7364494E44657661LLU, // sd_Deva_IN + 0x7364494E4B686F6ALLU, // sd_Khoj_IN + 0x7364494E53696E64LLU, // sd_Sind_IN + 0x887249544C61746ELLU, // sdc_Latn_IT + 0x9C72495241726162LLU, // sdh_Arab_IR + 0x73654E4F4C61746ELLU, // se_Latn_NO + 0x949243494C61746ELLU, // sef_Latn_CI + 0x9C924D5A4C61746ELLU, // seh_Latn_MZ + 0xA0924D584C61746ELLU, // sei_Latn_MX + 0xC8924D4C4C61746ELLU, // ses_Latn_ML + 0x736743464C61746ELLU, // sg_Latn_CF + 0x80D249454F67616DLLU, // sga_Ogam_IE + 0xC8D24C544C61746ELLU, // sgs_Latn_LT + 0xA0F24D4154666E67LLU, // shi_Tfng_MA + 0xB4F24D4D4D796D72LLU, // shn_Mymr_MM + 0x73694C4B53696E68LLU, // si_Sinh_LK + 0x8D1245544C61746ELLU, // sid_Latn_ET + 0x736B534B4C61746ELLU, // sk_Latn_SK + 0xC552504B41726162LLU, // skr_Arab_PK + 0x736C53494C61746ELLU, // sl_Latn_SI + 0xA172504C4C61746ELLU, // sli_Latn_PL + 0xE17249444C61746ELLU, // sly_Latn_ID + 0x736D57534C61746ELLU, // sm_Latn_WS + 0x819253454C61746ELLU, // sma_Latn_SE + 0xA59253454C61746ELLU, // smj_Latn_SE + 0xB59246494C61746ELLU, // smn_Latn_FI + 0xBD92494C53616D72LLU, // smp_Samr_IL + 0xC99246494C61746ELLU, // sms_Latn_FI + 0x736E5A574C61746ELLU, // sn_Latn_ZW + 0xA9B24D4C4C61746ELLU, // snk_Latn_ML + 0x736F534F4C61746ELLU, // so_Latn_SO + 0xD1D2544854686169LLU, // sou_Thai_TH + 0x7371414C4C61746ELLU, // sq_Latn_AL + 0x737252534379726CLLU, // sr_Cyrl_RS + 0x737252534C61746ELLU, // sr_Latn_RS + 0x8632494E536F7261LLU, // srb_Sora_IN + 0xB63253524C61746ELLU, // srn_Latn_SR + 0xC632534E4C61746ELLU, // srr_Latn_SN + 0xDE32494E44657661LLU, // srx_Deva_IN + 0x73735A414C61746ELLU, // ss_Latn_ZA + 0xE25245524C61746ELLU, // ssy_Latn_ER + 0x73745A414C61746ELLU, // st_Latn_ZA + 0xC27244454C61746ELLU, // stq_Latn_DE + 0x737549444C61746ELLU, // su_Latn_ID + 0xAA92545A4C61746ELLU, // suk_Latn_TZ + 0xCA92474E4C61746ELLU, // sus_Latn_GN + 0x737653454C61746ELLU, // sv_Latn_SE + 0x7377545A4C61746ELLU, // sw_Latn_TZ + 0x86D2595441726162LLU, // swb_Arab_YT + 0x8AD243444C61746ELLU, // swc_Latn_CD + 0x9AD244454C61746ELLU, // swg_Latn_DE + 0xD6D2494E44657661LLU, // swv_Deva_IN + 0xB6F249444C61746ELLU, // sxn_Latn_ID + 0xAF12424442656E67LLU, // syl_Beng_BD + 0xC712495153797263LLU, // syr_Syrc_IQ + 0xAF32504C4C61746ELLU, // szl_Latn_PL + 0x7461494E54616D6CLLU, // ta_Taml_IN + 0xA4134E5044657661LLU, // taj_Deva_NP + 0xD83350484C61746ELLU, // tbw_Latn_PH + 0xE053494E4B6E6461LLU, // tcy_Knda_IN + 0x8C73434E54616C65LLU, // tdd_Tale_CN + 0x98734E5044657661LLU, // tdg_Deva_NP + 0x9C734E5044657661LLU, // tdh_Deva_NP + 0x7465494E54656C75LLU, // te_Telu_IN + 0xB093534C4C61746ELLU, // tem_Latn_SL + 0xB89355474C61746ELLU, // teo_Latn_UG + 0xCC93544C4C61746ELLU, // tet_Latn_TL + 0x7467504B41726162LLU, // tg_Arab_PK + 0x7467544A4379726CLLU, // tg_Cyrl_TJ + 0x7468544854686169LLU, // th_Thai_TH + 0xACF34E5044657661LLU, // thl_Deva_NP + 0xC0F34E5044657661LLU, // thq_Deva_NP + 0xC4F34E5044657661LLU, // thr_Deva_NP + 0x7469455445746869LLU, // ti_Ethi_ET + 0x9913455245746869LLU, // tig_Ethi_ER + 0xD5134E474C61746ELLU, // tiv_Latn_NG + 0x746B544D4C61746ELLU, // tk_Latn_TM + 0xAD53544B4C61746ELLU, // tkl_Latn_TK + 0xC553415A4C61746ELLU, // tkr_Latn_AZ + 0xCD534E5044657661LLU, // tkt_Deva_NP + 0x746C50484C61746ELLU, // tl_Latn_PH + 0xE173415A4C61746ELLU, // tly_Latn_AZ + 0x9D934E454C61746ELLU, // tmh_Latn_NE + 0x746E5A414C61746ELLU, // tn_Latn_ZA + 0x746F544F4C61746ELLU, // to_Latn_TO + 0x99D34D574C61746ELLU, // tog_Latn_MW + 0xA1F350474C61746ELLU, // tpi_Latn_PG + 0x747254524C61746ELLU, // tr_Latn_TR + 0xD23354524C61746ELLU, // tru_Latn_TR + 0xD63354574C61746ELLU, // trv_Latn_TW + 0x74735A414C61746ELLU, // ts_Latn_ZA + 0x8E5347524772656BLLU, // tsd_Grek_GR + 0x96534E5044657661LLU, // tsf_Deva_NP + 0x9A5350484C61746ELLU, // tsg_Latn_PH + 0xA653425454696274LLU, // tsj_Tibt_BT + 0x747452554379726CLLU, // tt_Cyrl_RU + 0xA67355474C61746ELLU, // ttj_Latn_UG + 0xCA73544854686169LLU, // tts_Thai_TH + 0xCE73415A4C61746ELLU, // ttt_Latn_AZ + 0xB2934D574C61746ELLU, // tum_Latn_MW + 0xAEB354564C61746ELLU, // tvl_Latn_TV + 0xC2D34E454C61746ELLU, // twq_Latn_NE + 0x9AF3434E54616E67LLU, // txg_Tang_CN + 0x747950464C61746ELLU, // ty_Latn_PF + 0xD71352554379726CLLU, // tyv_Cyrl_RU + 0xB3334D414C61746ELLU, // tzm_Latn_MA + 0xB07452554379726CLLU, // udm_Cyrl_RU + 0x7567434E41726162LLU, // ug_Arab_CN + 0x75674B5A4379726CLLU, // ug_Cyrl_KZ + 0x80D4535955676172LLU, // uga_Ugar_SY + 0x756B55414379726CLLU, // uk_Cyrl_UA + 0xA174464D4C61746ELLU, // uli_Latn_FM + 0x8594414F4C61746ELLU, // umb_Latn_AO + 0xC5B4494E42656E67LLU, // unr_Beng_IN + 0xC5B44E5044657661LLU, // unr_Deva_NP + 0xDDB4494E42656E67LLU, // unx_Beng_IN + 0x7572504B41726162LLU, // ur_Arab_PK + 0x757A414641726162LLU, // uz_Arab_AF + 0x757A555A4C61746ELLU, // uz_Latn_UZ + 0xA0154C5256616969LLU, // vai_Vaii_LR + 0x76655A414C61746ELLU, // ve_Latn_ZA + 0x889549544C61746ELLU, // vec_Latn_IT + 0xBC9552554C61746ELLU, // vep_Latn_RU + 0x7669564E4C61746ELLU, // vi_Latn_VN + 0x891553584C61746ELLU, // vic_Latn_SX + 0xC97542454C61746ELLU, // vls_Latn_BE + 0x959544454C61746ELLU, // vmf_Latn_DE + 0xD9954D5A4C61746ELLU, // vmw_Latn_MZ + 0xCDD552554C61746ELLU, // vot_Latn_RU + 0xBA3545454C61746ELLU, // vro_Latn_EE + 0xB695545A4C61746ELLU, // vun_Latn_TZ + 0x776142454C61746ELLU, // wa_Latn_BE + 0x901643484C61746ELLU, // wae_Latn_CH + 0xAC16455445746869LLU, // wal_Ethi_ET + 0xC41650484C61746ELLU, // war_Latn_PH + 0xBC3641554C61746ELLU, // wbp_Latn_AU + 0xC036494E54656C75LLU, // wbq_Telu_IN + 0xC436494E44657661LLU, // wbr_Deva_IN + 0xC97657464C61746ELLU, // wls_Latn_WF + 0xA1B64B4D41726162LLU, // wni_Arab_KM + 0x776F534E4C61746ELLU, // wo_Latn_SN + 0xB276494E44657661LLU, // wtm_Deva_IN + 0xD296434E48616E73LLU, // wuu_Hans_CN + 0xD41742524C61746ELLU, // xav_Latn_BR + 0xC457545243617269LLU, // xcr_Cari_TR + 0x78685A414C61746ELLU, // xh_Latn_ZA + 0x897754524C796369LLU, // xlc_Lyci_TR + 0x8D7754524C796469LLU, // xld_Lydi_TR + 0x9597474547656F72LLU, // xmf_Geor_GE + 0xB597434E4D616E69LLU, // xmn_Mani_CN + 0xC59753444D657263LLU, // xmr_Merc_SD + 0x81B753414E617262LLU, // xna_Narb_SA + 0xC5B7494E44657661LLU, // xnr_Deva_IN + 0x99D755474C61746ELLU, // xog_Latn_UG + 0xC5F7495250727469LLU, // xpr_Prti_IR + 0x8257594553617262LLU, // xsa_Sarb_YE + 0xC6574E5044657661LLU, // xsr_Deva_NP + 0xB8184D5A4C61746ELLU, // yao_Latn_MZ + 0xBC18464D4C61746ELLU, // yap_Latn_FM + 0xD418434D4C61746ELLU, // yav_Latn_CM + 0x8438434D4C61746ELLU, // ybb_Latn_CM + 0x796F4E474C61746ELLU, // yo_Latn_NG + 0xAE3842524C61746ELLU, // yrl_Latn_BR + 0x82984D584C61746ELLU, // yua_Latn_MX + 0x9298434E48616E73LLU, // yue_Hans_CN + 0x9298484B48616E74LLU, // yue_Hant_HK + 0x7A61434E4C61746ELLU, // za_Latn_CN + 0x981953444C61746ELLU, // zag_Latn_SD + 0xA4794B4D41726162LLU, // zdj_Arab_KM + 0x80994E4C4C61746ELLU, // zea_Latn_NL + 0x9CD94D4154666E67LLU, // zgh_Tfng_MA + 0x7A685457426F706FLLU, // zh_Bopo_TW + 0x7A68545748616E62LLU, // zh_Hanb_TW + 0x7A68434E48616E73LLU, // zh_Hans_CN + 0x7A68545748616E74LLU, // zh_Hant_TW + 0xB17954474C61746ELLU, // zlm_Latn_TG + 0xA1994D594C61746ELLU, // zmi_Latn_MY + 0x7A755A414C61746ELLU, // zu_Latn_ZA + 0x833954524C61746ELLU, // zza_Latn_TR }); const std::unordered_map<uint32_t, uint32_t> ARAB_PARENTS({ diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS index 23ec5ab0d1f3..f1903a5a54a7 100644 --- a/libs/androidfw/OWNERS +++ b/libs/androidfw/OWNERS @@ -1,2 +1,3 @@ set noparent toddke@google.com +rtmitchell@google.com
\ No newline at end of file diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index ed7d5e578693..d22eaf38cef6 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -178,6 +178,7 @@ cc_defaults { "renderthread/CanvasContext.cpp", "renderthread/DrawFrameTask.cpp", "renderthread/EglManager.cpp", + "renderthread/ReliableSurface.cpp", "renderthread/VulkanManager.cpp", "renderthread/RenderProxy.cpp", "renderthread/RenderTask.cpp", diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index c63e449319dd..0b847af157d1 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -912,18 +912,6 @@ void RecordingCanvas::onDrawAnnotation(const SkRect& rect, const char key[], SkD fDL->drawAnnotation(rect, key, val); } -void RecordingCanvas::onDrawText(const void* text, size_t bytes, SkScalar x, SkScalar y, - const SkPaint& paint) { - fDL->drawText(text, bytes, x, y, paint); -} -void RecordingCanvas::onDrawPosText(const void* text, size_t bytes, const SkPoint pos[], - const SkPaint& paint) { - fDL->drawPosText(text, bytes, pos, paint); -} -void RecordingCanvas::onDrawPosTextH(const void* text, size_t bytes, const SkScalar xs[], - SkScalar y, const SkPaint& paint) { - fDL->drawPosTextH(text, bytes, xs, y, paint); -} void RecordingCanvas::onDrawTextRSXform(const void* text, size_t bytes, const SkRSXform xform[], const SkRect* cull, const SkPaint& paint) { fDL->drawTextRSXform(text, bytes, xform, cull, paint); diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 08cfc6266f56..35cf707665cb 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -170,10 +170,6 @@ public: void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override; void onDrawAnnotation(const SkRect&, const char[], SkData*) override; - void onDrawText(const void*, size_t, SkScalar x, SkScalar y, const SkPaint&) override; - void onDrawPosText(const void*, size_t, const SkPoint[], const SkPaint&) override; - void onDrawPosTextH(const void*, size_t, const SkScalar[], SkScalar, const SkPaint&) override; - void onDrawTextRSXform(const void*, size_t, const SkRSXform[], const SkRect*, const SkPaint&) override; void onDrawTextBlob(const SkTextBlob*, SkScalar, SkScalar, const SkPaint&) override; diff --git a/libs/hwui/pipeline/skia/DumpOpsCanvas.h b/libs/hwui/pipeline/skia/DumpOpsCanvas.h index 206219426bb0..2b5d58051c2c 100644 --- a/libs/hwui/pipeline/skia/DumpOpsCanvas.h +++ b/libs/hwui/pipeline/skia/DumpOpsCanvas.h @@ -82,18 +82,6 @@ protected: mOutput << mIdent << "drawDRRect" << std::endl; } - void onDrawText(const void*, size_t, SkScalar, SkScalar, const SkPaint&) override { - mOutput << mIdent << "drawText" << std::endl; - } - - void onDrawPosText(const void*, size_t, const SkPoint[], const SkPaint&) override { - mOutput << mIdent << "drawPosText" << std::endl; - } - - void onDrawPosTextH(const void*, size_t, const SkScalar[], SkScalar, const SkPaint&) override { - mOutput << mIdent << "drawPosTextH" << std::endl; - } - void onDrawTextRSXform(const void*, size_t, const SkRSXform[], const SkRect*, const SkPaint&) override { mOutput << mIdent << "drawTextRSXform" << std::endl; diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 142bca95e598..07979a22c988 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -155,7 +155,7 @@ void SkiaOpenGLPipeline::onStop() { } } -bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, +bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, ColorMode colorMode) { if (mEglSurface != EGL_NO_SURFACE) { mEglManager.destroySurface(mEglSurface); diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 4ab3541d447b..479910697871 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -42,7 +42,7 @@ public: bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; - bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior, + bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, renderthread::ColorMode colorMode) override; void onStop() override; bool isSurfaceReady() override; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index 3607b23a633e..437b5dc83f58 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -115,7 +115,7 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { void SkiaVulkanPipeline::onStop() {} -bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, +bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, ColorMode colorMode) { if (mVkSurface) { mVkManager.destroySurface(mVkSurface); diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 14c0d69dba33..02874c7d2c69 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -38,7 +38,7 @@ public: bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; - bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior, + bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, renderthread::ColorMode colorMode) override; void onStop() override; bool isSurfaceReady() override; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 6869972b5e7f..4e4262c5c0a7 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -142,7 +142,12 @@ void CanvasContext::destroy() { void CanvasContext::setSurface(sp<Surface>&& surface) { ATRACE_CALL(); - mNativeSurface = std::move(surface); + if (surface) { + mNativeSurface = new ReliableSurface{std::move(surface)}; + mNativeSurface->setDequeueTimeout(500_ms); + } else { + mNativeSurface = nullptr; + } ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::SRGB; bool hasSurface = mRenderPipeline->setSurface(mNativeSurface.get(), mSwapBehavior, colorMode); @@ -285,10 +290,11 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy info.damageAccumulator = &mDamageAccumulator; info.layerUpdateQueue = &mLayerUpdateQueue; + info.out.canDrawThisFrame = true; mAnimationContext->startFrame(info.mode); mRenderPipeline->onPrepareTree(); - for (const sp<RenderNode>& node : mRenderNodes) { + for (const sp<RenderNode> &node : mRenderNodes) { // Only the primary target node will be drawn full - all other nodes would get drawn in // real time mode. In case of a window, the primary node is the window content and the other // node(s) are non client / filler nodes. @@ -304,7 +310,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy mIsDirty = true; - if (CC_UNLIKELY(!mNativeSurface.get())) { + if (CC_UNLIKELY(!hasSurface())) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); info.out.canDrawThisFrame = false; return; @@ -312,7 +318,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy if (CC_LIKELY(mSwapHistory.size() && !Properties::forceDrawFrame)) { nsecs_t latestVsync = mRenderThread.timeLord().latestVsync(); - SwapHistory& lastSwap = mSwapHistory.back(); + SwapHistory &lastSwap = mSwapHistory.back(); nsecs_t vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync); // The slight fudge-factor is to deal with cases where // the vsync was estimated due to being slow handling the signal. @@ -333,7 +339,19 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy info.out.canDrawThisFrame = false; } - if (!info.out.canDrawThisFrame) { + if (info.out.canDrawThisFrame) { + int err = mNativeSurface->reserveNext(); + if (err != OK) { + mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); + info.out.canDrawThisFrame = false; + ALOGW("reserveNext failed, error = %d (%s)", err, strerror(-err)); + if (err != TIMED_OUT) { + // A timed out surface can still recover, but assume others are permanently dead. + setSurface(nullptr); + return; + } + } + } else { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 70be4a6d7730..9e7abf447cd6 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -25,6 +25,7 @@ #include "IRenderPipeline.h" #include "LayerUpdateQueue.h" #include "RenderNode.h" +#include "ReliableSurface.h" #include "renderthread/RenderTask.h" #include "renderthread/RenderThread.h" #include "thread/Task.h" @@ -219,7 +220,7 @@ private: EGLint mLastFrameHeight = 0; RenderThread& mRenderThread; - sp<Surface> mNativeSurface; + sp<ReliableSurface> mNativeSurface; // stopped indicates the CanvasContext will reject actual redraw operations, // and defer repaint until it is un-stopped bool mStopped = false; diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 65ced6ad9316..8230dfd44f9a 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -31,6 +31,8 @@ #include <string> #include <vector> +#include <system/window.h> +#include <gui/Surface.h> #define GLES_VERSION 2 @@ -106,7 +108,7 @@ void EglManager::initialize() { LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE, "Failed to initialize display %p! err=%s", mEglDisplay, eglErrorString()); - ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor); + ALOGV("Initialized EGL, version %d.%d", (int)major, (int)minor); initExtensions(); diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index 4972554c65cc..42e17b273bee 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -28,9 +28,9 @@ class GrContext; -namespace android { +struct ANativeWindow; -class Surface; +namespace android { namespace uirenderer { @@ -67,7 +67,7 @@ public: virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) = 0; virtual DeferredLayerUpdater* createTextureLayer() = 0; - virtual bool setSurface(Surface* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0; + virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0; virtual void onStop() = 0; virtual bool isSurfaceReady() = 0; virtual bool isContextReady() = 0; diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp new file mode 100644 index 000000000000..6f2b9df918e3 --- /dev/null +++ b/libs/hwui/renderthread/ReliableSurface.cpp @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2018 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 "ReliableSurface.h" + +#include <private/android/AHardwareBufferHelpers.h> + +namespace android::uirenderer::renderthread { + +// TODO: Re-enable after addressing more of the TODO's +// With this disabled we won't have a good up-front signal that the surface is no longer valid, +// however we can at least handle that reactively post-draw. There's just not a good mechanism +// to propagate this error back to the caller +constexpr bool DISABLE_BUFFER_PREFETCH = true; + +// TODO: Make surface less protected +// This exists because perform is a varargs, and ANativeWindow has no va_list perform. +// So wrapping/chaining that is hard. Telling the compiler to ignore protected is easy, so we do +// that instead +struct SurfaceExposer : Surface { + // Make warnings happy + SurfaceExposer() = delete; + + using Surface::setBufferCount; + using Surface::setSwapInterval; + using Surface::dequeueBuffer; + using Surface::queueBuffer; + using Surface::cancelBuffer; + using Surface::lockBuffer_DEPRECATED; + using Surface::perform; +}; + +#define callProtected(surface, func, ...) ((*surface).*&SurfaceExposer::func)(__VA_ARGS__) + +ReliableSurface::ReliableSurface(sp<Surface>&& surface) : mSurface(std::move(surface)) { + LOG_ALWAYS_FATAL_IF(!mSurface, "Error, unable to wrap a nullptr"); + + ANativeWindow::setSwapInterval = hook_setSwapInterval; + ANativeWindow::dequeueBuffer = hook_dequeueBuffer; + ANativeWindow::cancelBuffer = hook_cancelBuffer; + ANativeWindow::queueBuffer = hook_queueBuffer; + ANativeWindow::query = hook_query; + ANativeWindow::perform = hook_perform; + + ANativeWindow::dequeueBuffer_DEPRECATED = hook_dequeueBuffer_DEPRECATED; + ANativeWindow::cancelBuffer_DEPRECATED = hook_cancelBuffer_DEPRECATED; + ANativeWindow::lockBuffer_DEPRECATED = hook_lockBuffer_DEPRECATED; + ANativeWindow::queueBuffer_DEPRECATED = hook_queueBuffer_DEPRECATED; +} + +ReliableSurface::~ReliableSurface() { + clearReservedBuffer(); +} + +void ReliableSurface::perform(int operation, va_list args) { + std::lock_guard _lock{mMutex}; + + switch (operation) { + case NATIVE_WINDOW_SET_USAGE: + mUsage = va_arg(args, uint32_t); + break; + case NATIVE_WINDOW_SET_USAGE64: + mUsage = va_arg(args, uint64_t); + break; + case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY: + /* width */ va_arg(args, uint32_t); + /* height */ va_arg(args, uint32_t); + mFormat = va_arg(args, PixelFormat); + break; + case NATIVE_WINDOW_SET_BUFFERS_FORMAT: + mFormat = va_arg(args, PixelFormat); + break; + } +} + +int ReliableSurface::reserveNext() { + { + std::lock_guard _lock{mMutex}; + if (mReservedBuffer) { + ALOGW("reserveNext called but there was already a buffer reserved?"); + return OK; + } + if (mInErrorState) { + return UNKNOWN_ERROR; + } + if (mHasDequeuedBuffer) { + return OK; + } + if constexpr (DISABLE_BUFFER_PREFETCH) { + return OK; + } + } + + // TODO: Update this to better handle when requested dimensions have changed + // Currently the driver does this via query + perform but that's after we've already + // reserved a buffer. Should we do that logic instead? Or should we drop + // the backing Surface to the ground and go full manual on the IGraphicBufferProducer instead? + + int fenceFd = -1; + ANativeWindowBuffer* buffer = nullptr; + int result = callProtected(mSurface, dequeueBuffer, &buffer, &fenceFd); + + { + std::lock_guard _lock{mMutex}; + LOG_ALWAYS_FATAL_IF(mReservedBuffer, "race condition in reserveNext"); + mReservedBuffer = buffer; + mReservedFenceFd.reset(fenceFd); + } + + return result; +} + +void ReliableSurface::clearReservedBuffer() { + ANativeWindowBuffer* buffer = nullptr; + int releaseFd = -1; + { + std::lock_guard _lock{mMutex}; + if (mReservedBuffer) { + ALOGW("Reserved buffer %p was never used", mReservedBuffer); + buffer = mReservedBuffer; + releaseFd = mReservedFenceFd.release(); + } + mReservedBuffer = nullptr; + mReservedFenceFd.reset(); + mHasDequeuedBuffer = false; + } + if (buffer) { + callProtected(mSurface, cancelBuffer, buffer, releaseFd); + } +} + +int ReliableSurface::cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd) { + clearReservedBuffer(); + if (isFallbackBuffer(buffer)) { + if (fenceFd > 0) { + close(fenceFd); + } + return OK; + } + int result = callProtected(mSurface, cancelBuffer, buffer, fenceFd); + return result; +} + +int ReliableSurface::dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd) { + { + std::lock_guard _lock{mMutex}; + if (mReservedBuffer) { + *buffer = mReservedBuffer; + *fenceFd = mReservedFenceFd.release(); + mReservedBuffer = nullptr; + return OK; + } + } + + int result = callProtected(mSurface, dequeueBuffer, buffer, fenceFd); + if (result != OK) { + ALOGW("dequeueBuffer failed, error = %d; switching to fallback", result); + *buffer = acquireFallbackBuffer(); + *fenceFd = -1; + return *buffer ? OK : INVALID_OPERATION; + } else { + std::lock_guard _lock{mMutex}; + mHasDequeuedBuffer = true; + } + return OK; +} + +int ReliableSurface::queueBuffer(ANativeWindowBuffer* buffer, int fenceFd) { + clearReservedBuffer(); + + if (isFallbackBuffer(buffer)) { + if (fenceFd > 0) { + close(fenceFd); + } + return OK; + } + + int result = callProtected(mSurface, queueBuffer, buffer, fenceFd); + return result; +} + +bool ReliableSurface::isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const { + if (!mScratchBuffer || !windowBuffer) { + return false; + } + ANativeWindowBuffer* scratchBuffer = + AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get()); + return windowBuffer == scratchBuffer; +} + +ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer() { + std::lock_guard _lock{mMutex}; + mInErrorState = true; + + if (mScratchBuffer) { + return AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get()); + } + + AHardwareBuffer_Desc desc; + desc.usage = mUsage; + desc.format = mFormat; + desc.width = 1; + desc.height = 1; + desc.layers = 1; + desc.rfu0 = 0; + desc.rfu1 = 0; + AHardwareBuffer* newBuffer = nullptr; + int err = AHardwareBuffer_allocate(&desc, &newBuffer); + if (err) { + // Allocate failed, that sucks + ALOGW("Failed to allocate scratch buffer, error=%d", err); + return nullptr; + } + mScratchBuffer.reset(newBuffer); + return AHardwareBuffer_to_ANativeWindowBuffer(newBuffer); +} + +Surface* ReliableSurface::getWrapped(const ANativeWindow* window) { + return getSelf(window)->mSurface.get(); +} + +int ReliableSurface::hook_setSwapInterval(ANativeWindow* window, int interval) { + return callProtected(getWrapped(window), setSwapInterval, interval); +} + +int ReliableSurface::hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, + int* fenceFd) { + return getSelf(window)->dequeueBuffer(buffer, fenceFd); +} + +int ReliableSurface::hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, + int fenceFd) { + return getSelf(window)->cancelBuffer(buffer, fenceFd); +} + +int ReliableSurface::hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, + int fenceFd) { + return getSelf(window)->queueBuffer(buffer, fenceFd); +} + +int ReliableSurface::hook_dequeueBuffer_DEPRECATED(ANativeWindow* window, + ANativeWindowBuffer** buffer) { + ANativeWindowBuffer* buf; + int fenceFd = -1; + int result = window->dequeueBuffer(window, &buf, &fenceFd); + if (result != OK) { + return result; + } + sp<Fence> fence(new Fence(fenceFd)); + int waitResult = fence->waitForever("dequeueBuffer_DEPRECATED"); + if (waitResult != OK) { + ALOGE("dequeueBuffer_DEPRECATED: Fence::wait returned an error: %d", waitResult); + window->cancelBuffer(window, buf, -1); + return waitResult; + } + *buffer = buf; + return result; +} + +int ReliableSurface::hook_cancelBuffer_DEPRECATED(ANativeWindow* window, + ANativeWindowBuffer* buffer) { + return window->cancelBuffer(window, buffer, -1); +} + +int ReliableSurface::hook_lockBuffer_DEPRECATED(ANativeWindow* window, + ANativeWindowBuffer* buffer) { + // This method is a no-op in Surface as well + return OK; +} + +int ReliableSurface::hook_queueBuffer_DEPRECATED(ANativeWindow* window, + ANativeWindowBuffer* buffer) { + return window->queueBuffer(window, buffer, -1); +} + +int ReliableSurface::hook_query(const ANativeWindow* window, int what, int* value) { + return getWrapped(window)->query(what, value); +} + +int ReliableSurface::hook_perform(ANativeWindow* window, int operation, ...) { + // Drop the reserved buffer if there is one since this (probably) mutated buffer dimensions + // TODO: Filter to things that only affect the reserved buffer + // TODO: Can we mutate the reserved buffer in some cases? + getSelf(window)->clearReservedBuffer(); + va_list args; + va_start(args, operation); + int result = callProtected(getWrapped(window), perform, operation, args); + va_end(args); + + switch (operation) { + case NATIVE_WINDOW_SET_BUFFERS_FORMAT: + case NATIVE_WINDOW_SET_USAGE: + case NATIVE_WINDOW_SET_USAGE64: + va_start(args, operation); + getSelf(window)->perform(operation, args); + va_end(args); + break; + default: + break; + } + + return result; +} + +}; // namespace android::uirenderer::renderthread
\ No newline at end of file diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h new file mode 100644 index 000000000000..0bfc72ef61cb --- /dev/null +++ b/libs/hwui/renderthread/ReliableSurface.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 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. + */ + +#pragma once + +#include <gui/Surface.h> +#include <utils/Macros.h> +#include <utils/StrongPointer.h> + +#include <memory> + +namespace android::uirenderer::renderthread { + +class ReliableSurface : public ANativeObjectBase<ANativeWindow, ReliableSurface, RefBase> { + PREVENT_COPY_AND_ASSIGN(ReliableSurface); + +public: + ReliableSurface(sp<Surface>&& surface); + ~ReliableSurface(); + + void setDequeueTimeout(nsecs_t timeout) { mSurface->setDequeueTimeout(timeout); } + + int reserveNext(); + + void allocateBuffers() { mSurface->allocateBuffers(); } + + int query(int what, int* value) const { return mSurface->query(what, value); } + + nsecs_t getLastDequeueStartTime() const { return mSurface->getLastDequeueStartTime(); } + + uint64_t getNextFrameNumber() const { return mSurface->getNextFrameNumber(); } + +private: + const sp<Surface> mSurface; + + mutable std::mutex mMutex; + + uint64_t mUsage = AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER; + PixelFormat mFormat = PIXEL_FORMAT_RGBA_8888; + std::unique_ptr<AHardwareBuffer, void (*)(AHardwareBuffer*)> mScratchBuffer{ + nullptr, AHardwareBuffer_release}; + ANativeWindowBuffer* mReservedBuffer = nullptr; + base::unique_fd mReservedFenceFd; + bool mHasDequeuedBuffer = false; + bool mInErrorState = false; + + bool isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const; + ANativeWindowBuffer* acquireFallbackBuffer(); + void clearReservedBuffer(); + + void perform(int operation, va_list args); + int cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd); + int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd); + int queueBuffer(ANativeWindowBuffer* buffer, int fenceFd); + + static Surface* getWrapped(const ANativeWindow*); + + // ANativeWindow hooks + static int hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd); + static int hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, + int* fenceFd); + static int hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd); + + static int hook_perform(ANativeWindow* window, int operation, ...); + static int hook_query(const ANativeWindow* window, int what, int* value); + static int hook_setSwapInterval(ANativeWindow* window, int interval); + + static int hook_cancelBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); + static int hook_dequeueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer** buffer); + static int hook_lockBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); + static int hook_queueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); +}; + +}; // namespace android::uirenderer::renderthread
\ No newline at end of file diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 2abb3d5179a0..4be8bd9a863e 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -831,6 +831,11 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { createBuffers(surface, surfaceFormat, extent); + // The window content is not updated (frozen) until a buffer of the window size is received. + // This prevents temporary stretching of the window after it is resized, but before the first + // buffer with new size is enqueued. + native_window_set_scaling_mode(surface->mNativeWindow, NATIVE_WINDOW_SCALING_MODE_FREEZE); + return true; } diff --git a/libs/hwui/tests/unit/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h index 89f0c52b49ec..146662b1e913 100644 --- a/libs/hwui/tests/unit/FatalTestCanvas.h +++ b/libs/hwui/tests/unit/FatalTestCanvas.h @@ -30,18 +30,6 @@ public: void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) { ADD_FAILURE() << "onDrawDRRect not expected in this test"; } - void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, - const SkPaint& paint) { - ADD_FAILURE() << "onDrawText not expected in this test"; - } - void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], - const SkPaint& paint) { - ADD_FAILURE() << "onDrawPosText not expected in this test"; - } - void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], SkScalar constY, - const SkPaint& paint) { - ADD_FAILURE() << "onDrawPosTextH not expected in this test"; - } void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform[], const SkRect* cullRect, const SkPaint& paint) { ADD_FAILURE() << "onDrawTextRSXform not expected in this test"; diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp index fbc21e558806..26261ef929ae 100644 --- a/libs/incident/src/IncidentReportArgs.cpp +++ b/libs/incident/src/IncidentReportArgs.cpp @@ -18,7 +18,7 @@ #include <android/os/IncidentReportArgs.h> -#include <cutils/log.h> +#include <log/log.h> namespace android { namespace os { diff --git a/libs/input/Android.bp b/libs/input/Android.bp index f1d9397783ed..89d3cc4f5083 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -14,7 +14,6 @@ cc_library_shared { name: "libinputservice", - srcs: [ "PointerController.cpp", "SpriteController.cpp", diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 0a90f85cda0e..80d8e72a87e2 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -89,10 +89,6 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& mLocked.animationPending = false; - mLocked.displayWidth = -1; - mLocked.displayHeight = -1; - mLocked.displayOrientation = DISPLAY_ORIENTATION_0; - mLocked.presentation = PRESENTATION_POINTER; mLocked.presentationChanged = false; @@ -110,15 +106,6 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& mLocked.lastFrameUpdatedTime = 0; mLocked.buttonState = 0; - - mPolicy->loadPointerIcon(&mLocked.pointerIcon); - - loadResources(); - - if (mLocked.pointerIcon.isValid()) { - mLocked.pointerIconChanged = true; - updatePointerLocked(); - } } PointerController::~PointerController() { @@ -144,23 +131,15 @@ bool PointerController::getBounds(float* outMinX, float* outMinY, bool PointerController::getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const { - if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) { + + if (!mLocked.viewport.isValid()) { return false; } - *outMinX = 0; - *outMinY = 0; - switch (mLocked.displayOrientation) { - case DISPLAY_ORIENTATION_90: - case DISPLAY_ORIENTATION_270: - *outMaxX = mLocked.displayHeight - 1; - *outMaxY = mLocked.displayWidth - 1; - break; - default: - *outMaxX = mLocked.displayWidth - 1; - *outMaxY = mLocked.displayHeight - 1; - break; - } + *outMinX = mLocked.viewport.logicalLeft; + *outMinY = mLocked.viewport.logicalTop; + *outMaxX = mLocked.viewport.logicalRight - 1; + *outMaxY = mLocked.viewport.logicalBottom - 1; return true; } @@ -231,6 +210,12 @@ void PointerController::getPosition(float* outX, float* outY) const { *outY = mLocked.pointerY; } +int32_t PointerController::getDisplayId() const { + AutoMutex _l(mLock); + + return mLocked.viewport.displayId; +} + void PointerController::fade(Transition transition) { AutoMutex _l(mLock); @@ -355,48 +340,57 @@ void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout void PointerController::reloadPointerResources() { AutoMutex _l(mLock); - loadResources(); + loadResourcesLocked(); + updatePointerLocked(); +} - if (mLocked.presentation == PRESENTATION_POINTER) { - mLocked.additionalMouseResources.clear(); - mLocked.animationResources.clear(); - mPolicy->loadPointerIcon(&mLocked.pointerIcon); - mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, - &mLocked.animationResources); +/** + * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation, + * so here we are getting the dimensions in the original, unrotated orientation (orientation 0). + */ +static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) { + if (viewport.orientation == DISPLAY_ORIENTATION_90 + || viewport.orientation == DISPLAY_ORIENTATION_270) { + width = viewport.deviceHeight; + height = viewport.deviceWidth; + } else { + width = viewport.deviceWidth; + height = viewport.deviceHeight; } - - mLocked.presentationChanged = true; - updatePointerLocked(); } -void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) { +void PointerController::setDisplayViewport(const DisplayViewport& viewport) { AutoMutex _l(mLock); - - // Adjust to use the display's unrotated coordinate frame. - if (orientation == DISPLAY_ORIENTATION_90 - || orientation == DISPLAY_ORIENTATION_270) { - int32_t temp = height; - height = width; - width = temp; + if (viewport == mLocked.viewport) { + return; } - if (mLocked.displayWidth != width || mLocked.displayHeight != height) { - mLocked.displayWidth = width; - mLocked.displayHeight = height; + const DisplayViewport oldViewport = mLocked.viewport; + mLocked.viewport = viewport; + + int32_t oldDisplayWidth, oldDisplayHeight; + getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight); + int32_t newDisplayWidth, newDisplayHeight; + getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight); + + // Reset cursor position to center if size or display changed. + if (oldViewport.displayId != viewport.displayId + || oldDisplayWidth != newDisplayWidth + || oldDisplayHeight != newDisplayHeight) { float minX, minY, maxX, maxY; if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) { mLocked.pointerX = (minX + maxX) * 0.5f; mLocked.pointerY = (minY + maxY) * 0.5f; + // Reload icon resources for density may be changed. + loadResourcesLocked(); } else { mLocked.pointerX = 0; mLocked.pointerY = 0; } fadeOutAndReleaseAllSpotsLocked(); - } - - if (mLocked.displayOrientation != orientation) { + } else if (oldViewport.orientation != viewport.orientation) { // Apply offsets to convert from the pixel top-left corner position to the pixel center. // This creates an invariant frame of reference that we can easily rotate when // taking into account that the pointer may be located at fractional pixel offsets. @@ -405,37 +399,37 @@ void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_ float temp; // Undo the previous rotation. - switch (mLocked.displayOrientation) { + switch (oldViewport.orientation) { case DISPLAY_ORIENTATION_90: temp = x; - x = mLocked.displayWidth - y; + x = oldViewport.deviceHeight - y; y = temp; break; case DISPLAY_ORIENTATION_180: - x = mLocked.displayWidth - x; - y = mLocked.displayHeight - y; + x = oldViewport.deviceWidth - x; + y = oldViewport.deviceHeight - y; break; case DISPLAY_ORIENTATION_270: temp = x; x = y; - y = mLocked.displayHeight - temp; + y = oldViewport.deviceWidth - temp; break; } // Perform the new rotation. - switch (orientation) { + switch (viewport.orientation) { case DISPLAY_ORIENTATION_90: temp = x; x = y; - y = mLocked.displayWidth - temp; + y = viewport.deviceHeight - temp; break; case DISPLAY_ORIENTATION_180: - x = mLocked.displayWidth - x; - y = mLocked.displayHeight - y; + x = viewport.deviceWidth - x; + y = viewport.deviceHeight - y; break; case DISPLAY_ORIENTATION_270: temp = x; - x = mLocked.displayHeight - y; + x = viewport.deviceWidth - y; y = temp; break; } @@ -444,7 +438,6 @@ void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_ // and save the results. mLocked.pointerX = x - 0.5f; mLocked.pointerY = y - 0.5f; - mLocked.displayOrientation = orientation; } updatePointerLocked(); @@ -614,11 +607,16 @@ void PointerController::removeInactivityTimeoutLocked() { mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT); } -void PointerController::updatePointerLocked() { +void PointerController::updatePointerLocked() REQUIRES(mLock) { + if (!mLocked.viewport.isValid()) { + return; + } + mSpriteController->openTransaction(); mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER); mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY); + mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId); if (mLocked.pointerAlpha > 0) { mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha); @@ -729,8 +727,18 @@ void PointerController::fadeOutAndReleaseAllSpotsLocked() { } } -void PointerController::loadResources() { +void PointerController::loadResourcesLocked() REQUIRES(mLock) { mPolicy->loadPointerResources(&mResources); + + if (mLocked.presentation == PRESENTATION_POINTER) { + mLocked.additionalMouseResources.clear(); + mLocked.animationResources.clear(); + mPolicy->loadPointerIcon(&mLocked.pointerIcon); + mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, + &mLocked.animationResources); + } + + mLocked.pointerIconChanged = true; } diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 7f4e5a59c9b6..a32cc42a3342 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -23,6 +23,7 @@ #include <vector> #include <ui/DisplayInfo.h> +#include <input/DisplayViewport.h> #include <input/Input.h> #include <PointerControllerInterface.h> #include <utils/BitSet.h> @@ -96,6 +97,7 @@ public: virtual int32_t getButtonState() const; virtual void setPosition(float x, float y); virtual void getPosition(float* outX, float* outY) const; + virtual int32_t getDisplayId() const; virtual void fade(Transition transition); virtual void unfade(Transition transition); @@ -106,7 +108,7 @@ public: void updatePointerIcon(int32_t iconId); void setCustomPointerIcon(const SpriteIcon& icon); - void setDisplayViewport(int32_t width, int32_t height, int32_t orientation); + void setDisplayViewport(const DisplayViewport& viewport); void setInactivityTimeout(InactivityTimeout inactivityTimeout); void reloadPointerResources(); @@ -156,9 +158,7 @@ private: size_t animationFrameIndex; nsecs_t lastFrameUpdatedTime; - int32_t displayWidth; - int32_t displayHeight; - int32_t displayOrientation; + DisplayViewport viewport; InactivityTimeout inactivityTimeout; @@ -182,7 +182,7 @@ private: Vector<Spot*> spots; Vector<sp<Sprite> > recycledSprites; - } mLocked; + } mLocked GUARDED_BY(mLock); bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; void setPositionLocked(float x, float y); @@ -207,7 +207,7 @@ private: void fadeOutAndReleaseSpotLocked(Spot* spot); void fadeOutAndReleaseAllSpotsLocked(); - void loadResources(); + void loadResourcesLocked(); }; } // namespace android diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index eb2bc98ec9e9..c1868d3a94d6 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -144,13 +144,16 @@ void SpriteController::doUpdateSprites() { } } - // Resize sprites if needed. + // Resize and/or reparent sprites if needed. SurfaceComposerClient::Transaction t; bool needApplyTransaction = false; for (size_t i = 0; i < numSprites; i++) { SpriteUpdate& update = updates.editItemAt(i); + if (update.state.surfaceControl == nullptr) { + continue; + } - if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) { + if (update.state.wantSurfaceVisible()) { int32_t desiredWidth = update.state.icon.bitmap.width(); int32_t desiredHeight = update.state.icon.bitmap.height(); if (update.state.surfaceWidth < desiredWidth @@ -170,6 +173,12 @@ void SpriteController::doUpdateSprites() { } } } + + // If surface is a new one, we have to set right layer stack. + if (update.surfaceChanged || update.state.dirty & DIRTY_DISPLAY_ID) { + t.setLayerStack(update.state.surfaceControl, update.state.displayId); + needApplyTransaction = true; + } } if (needApplyTransaction) { t.apply(); @@ -236,7 +245,7 @@ void SpriteController::doUpdateSprites() { if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER - | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) { + | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID))))) { needApplyTransaction = true; if (wantSurfaceVisibleAndDrawn @@ -445,6 +454,15 @@ void SpriteController::SpriteImpl::setTransformationMatrix( } } +void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.displayId != displayId) { + mLocked.state.displayId = displayId; + invalidateLocked(DIRTY_DISPLAY_ID); + } +} + void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) { bool wasDirty = mLocked.state.dirty; mLocked.state.dirty |= dirty; diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index 31e43e9b99e5..5b216f50d113 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -125,6 +125,9 @@ public: /* Sets the sprite transformation matrix. */ virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0; + + /* Sets the id of the display where the sprite should be shown. */ + virtual void setDisplayId(int32_t displayId) = 0; }; /* @@ -170,6 +173,7 @@ private: DIRTY_LAYER = 1 << 4, DIRTY_VISIBILITY = 1 << 5, DIRTY_HOTSPOT = 1 << 6, + DIRTY_DISPLAY_ID = 1 << 7, }; /* Describes the state of a sprite. @@ -180,7 +184,7 @@ private: struct SpriteState { inline SpriteState() : dirty(0), visible(false), - positionX(0), positionY(0), layer(0), alpha(1.0f), + positionX(0), positionY(0), layer(0), alpha(1.0f), displayId(ADISPLAY_ID_DEFAULT), surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) { } @@ -193,6 +197,7 @@ private: int32_t layer; float alpha; SpriteTransformationMatrix transformationMatrix; + int32_t displayId; sp<SurfaceControl> surfaceControl; int32_t surfaceWidth; @@ -225,6 +230,7 @@ private: virtual void setLayer(int32_t layer); virtual void setAlpha(float alpha); virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix); + virtual void setDisplayId(int32_t displayId); inline const SpriteState& getStateLocked() const { return mLocked.state; diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index ae87998a1615..32c752064a69 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -96,9 +96,7 @@ interface ILocationManager void addTestProvider(String name, in ProviderProperties properties, String opPackageName); void removeTestProvider(String provider, String opPackageName); void setTestProviderLocation(String provider, in Location loc, String opPackageName); - void clearTestProviderLocation(String provider, String opPackageName); void setTestProviderEnabled(String provider, boolean enabled, String opPackageName); - void clearTestProviderEnabled(String provider, String opPackageName); // --- deprecated --- void setTestProviderStatus(String provider, int status, in Bundle extras, long updateTime, @@ -108,12 +106,8 @@ interface ILocationManager // --- internal --- - // Used by location providers to tell the location manager when it has a new location. - // Passive is true if the location is coming from the passive provider, in which case - // it need not be shared with other providers. + // --- deprecated --- void reportLocation(in Location location, boolean passive); - - // Used when a (initially Gnss) Location batch arrives void reportLocationBatch(in List<Location> locations); // for reporting callback completion diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 3bf98b352b40..334170e5ce03 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -1537,14 +1537,11 @@ public class LocationManager { * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED * allowed} for your app. * @throws IllegalArgumentException if no provider with the given name exists + * + * @deprecated This function has always been a no-op, and may be removed in the future. */ - public void clearTestProviderLocation(String provider) { - try { - mService.clearTestProviderLocation(provider, mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } + @Deprecated + public void clearTestProviderLocation(String provider) {} /** * Sets a mock enabled value for the given provider. This value will be used in place @@ -1575,13 +1572,12 @@ public class LocationManager { * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED * allowed} for your app. * @throws IllegalArgumentException if no provider with the given name exists + * + * @deprecated Use {@link #setTestProviderEnabled(String, boolean)} instead. */ + @Deprecated public void clearTestProviderEnabled(String provider) { - try { - mService.clearTestProviderEnabled(provider, mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + setTestProviderEnabled(provider, true); } /** diff --git a/location/java/com/android/internal/location/ILocationProvider.aidl b/location/java/com/android/internal/location/ILocationProvider.aidl index 39c2d92bf278..71b54fb65ae5 100644 --- a/location/java/com/android/internal/location/ILocationProvider.aidl +++ b/location/java/com/android/internal/location/ILocationProvider.aidl @@ -16,29 +16,26 @@ package com.android.internal.location; -import android.location.Location; -import android.net.NetworkInfo; import android.os.Bundle; import android.os.WorkSource; -import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ILocationProviderManager; import com.android.internal.location.ProviderRequest; /** - * Binder interface for services that implement location providers. - * <p>Use {@link LocationProviderBase} as a helper to implement this - * interface. + * Binder interface for services that implement location providers. Do not implement this directly, + * extend {@link LocationProviderBase} instead. * @hide */ interface ILocationProvider { - void enable(); - void disable(); - void setRequest(in ProviderRequest request, in WorkSource ws); + oneway void setLocationProviderManager(in ILocationProviderManager manager); - // --- deprecated (but still supported) --- - ProviderProperties getProperties(); + oneway void setRequest(in ProviderRequest request, in WorkSource ws); + + oneway void sendExtraCommand(String command, in Bundle extras); + + // --- deprecated and will be removed the future --- int getStatus(out Bundle extras); long getStatusUpdateTime(); - boolean sendExtraCommand(String command, inout Bundle extras); } diff --git a/location/java/android/location/IGnssStatusProvider.aidl b/location/java/com/android/internal/location/ILocationProviderManager.aidl index 006b5d3c0c20..b1b8f0c7c3f7 100644 --- a/location/java/android/location/IGnssStatusProvider.aidl +++ b/location/java/com/android/internal/location/ILocationProviderManager.aidl @@ -14,16 +14,21 @@ * limitations under the License. */ -package android.location; +package com.android.internal.location; -import android.location.IGnssStatusListener; +import android.location.Location; + +import com.android.internal.location.ProviderProperties; /** - * An interface for location providers that provide GNSS status information. - * - * {@hide} + * Binder interface for manager of all location providers. + * @hide */ -interface IGnssStatusProvider { - void registerGnssStatusCallback(IGnssStatusListener callback); - void unregisterGnssStatusCallback(IGnssStatusListener callback); +interface ILocationProviderManager { + + void onSetEnabled(boolean enabled); + + void onSetProperties(in ProviderProperties properties); + + void onReportLocation(in Location location); } diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java index 88919f628638..a45c20d9d09d 100644 --- a/location/java/com/android/internal/location/ProviderRequest.java +++ b/location/java/com/android/internal/location/ProviderRequest.java @@ -16,15 +16,15 @@ package com.android.internal.location; -import java.util.ArrayList; -import java.util.List; - import android.annotation.UnsupportedAppUsage; import android.location.LocationRequest; import android.os.Parcel; import android.os.Parcelable; import android.util.TimeUtils; +import java.util.ArrayList; +import java.util.List; + /** @hide */ public final class ProviderRequest implements Parcelable { /** Location reporting is requested (true) */ @@ -36,6 +36,13 @@ public final class ProviderRequest implements Parcelable { public long interval = Long.MAX_VALUE; /** + * When this flag is true, providers should ignore all location settings, user consents, power + * restrictions or any other restricting factors and always satisfy this request to the best of + * their ability. This flag should only be used in event of an emergency. + */ + public boolean forceLocation = false; + + /** * Whether provider shall make stronger than normal tradeoffs to substantially restrict power * use. */ diff --git a/location/lib/api/current.txt b/location/lib/api/current.txt index d19559e8cccd..10c344775019 100644 --- a/location/lib/api/current.txt +++ b/location/lib/api/current.txt @@ -8,14 +8,18 @@ package com.android.location.provider { public abstract class LocationProviderBase { ctor public LocationProviderBase(java.lang.String, com.android.location.provider.ProviderPropertiesUnbundled); method public android.os.IBinder getBinder(); - method public abstract void onDisable(); - method public void onDump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]); - method public abstract void onEnable(); - method public deprecated int onGetStatus(android.os.Bundle); - method public deprecated long onGetStatusUpdateTime(); - method public boolean onSendExtraCommand(java.lang.String, android.os.Bundle); - method public abstract void onSetRequest(com.android.location.provider.ProviderRequestUnbundled, android.os.WorkSource); - method public final void reportLocation(android.location.Location); + method public boolean isEnabled(); + method protected deprecated void onDisable(); + method protected void onDump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]); + method protected deprecated void onEnable(); + method protected deprecated int onGetStatus(android.os.Bundle); + method protected deprecated long onGetStatusUpdateTime(); + method protected void onInit(); + method protected boolean onSendExtraCommand(java.lang.String, android.os.Bundle); + method protected abstract void onSetRequest(com.android.location.provider.ProviderRequestUnbundled, android.os.WorkSource); + method public void reportLocation(android.location.Location); + method public void setEnabled(boolean); + method public void setProperties(com.android.location.provider.ProviderPropertiesUnbundled); field public static final java.lang.String EXTRA_NO_GPS_LOCATION = "noGPSLocation"; field public static final java.lang.String FUSED_PROVIDER = "fused"; } @@ -38,6 +42,7 @@ package com.android.location.provider { } public final class ProviderRequestUnbundled { + method public boolean getForceLocation(); method public long getInterval(); method public java.util.List<com.android.location.provider.LocationRequestUnbundled> getLocationRequests(); method public boolean getReportLocation(); diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java index d45a4bac8f96..5bcec92c4fba 100644 --- a/location/lib/java/com/android/location/provider/LocationProviderBase.java +++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java @@ -16,6 +16,7 @@ package com.android.location.provider; +import android.annotation.Nullable; import android.content.Context; import android.location.ILocationManager; import android.location.Location; @@ -29,12 +30,11 @@ import android.os.WorkSource; import android.util.Log; import com.android.internal.location.ILocationProvider; +import com.android.internal.location.ILocationProviderManager; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; -import com.android.internal.util.FastPrintWriter; import java.io.FileDescriptor; -import java.io.FileOutputStream; import java.io.PrintWriter; /** @@ -55,12 +55,6 @@ import java.io.PrintWriter; * of this package for more information. */ public abstract class LocationProviderBase { - private final String TAG; - - /** @hide */ - protected final ILocationManager mLocationManager; - private final ProviderProperties mProperties; - private final IBinder mBinder; /** * Bundle key for a version of the location containing no GPS data. @@ -77,49 +71,34 @@ public abstract class LocationProviderBase { */ public static final String FUSED_PROVIDER = LocationManager.FUSED_PROVIDER; - private final class Service extends ILocationProvider.Stub { - @Override - public void enable() { - onEnable(); - } - @Override - public void disable() { - onDisable(); - } - @Override - public void setRequest(ProviderRequest request, WorkSource ws) { - onSetRequest(new ProviderRequestUnbundled(request), ws); - } - @Override - public ProviderProperties getProperties() { - return mProperties; - } - @Override - public int getStatus(Bundle extras) { - return onGetStatus(extras); - } - @Override - public long getStatusUpdateTime() { - return onGetStatusUpdateTime(); - } - @Override - public boolean sendExtraCommand(String command, Bundle extras) { - return onSendExtraCommand(command, extras); - } - @Override - public void dump(FileDescriptor fd, String[] args) { - PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd)); - onDump(fd, pw, args); - pw.flush(); - } - } + private final String mTag; + private final IBinder mBinder; + + /** + * This field may be removed in the future, do not rely on it. + * + * @deprecated Do not use this field! Use LocationManager APIs instead. If you use this field + * you may be broken in the future. + * @hide + */ + @Deprecated + protected final ILocationManager mLocationManager; + + // write locked on mBinder, read lock is optional depending on atomicity requirements + @Nullable private volatile ILocationProviderManager mManager; + private volatile ProviderProperties mProperties; + private volatile boolean mEnabled; public LocationProviderBase(String tag, ProviderPropertiesUnbundled properties) { - TAG = tag; - IBinder b = ServiceManager.getService(Context.LOCATION_SERVICE); - mLocationManager = ILocationManager.Stub.asInterface(b); - mProperties = properties.getProviderProperties(); + mTag = tag; mBinder = new Service(); + + mLocationManager = ILocationManager.Stub.asInterface( + ServiceManager.getService(Context.LOCATION_SERVICE)); + + mManager = null; + mProperties = properties.getProviderProperties(); + mEnabled = true; } public IBinder getBinder() { @@ -127,51 +106,116 @@ public abstract class LocationProviderBase { } /** - * Used by the location provider to report new locations. + * Sets whether this provider is currently enabled or not. Note that this is specific to the + * provider only, and is not related to global location settings. This is a hint to the Location + * Manager that this provider will generally be unable to fulfill incoming requests. This + * provider may still receive callbacks to onSetRequest while not enabled, and must decide + * whether to attempt to satisfy those requests or not. * - * @param location new Location to report - * - * Requires the android.permission.INSTALL_LOCATION_PROVIDER permission. + * Some guidelines: providers should set their own enabled/disabled status based only on state + * "owned" by that provider. For instance, providers should not take into account the state of + * the location master setting when setting themselves enabled or disabled, as this state is not + * owned by a particular provider. If a provider requires some additional user consent that is + * particular to the provider, this should be use to set the enabled/disabled state. If the + * provider proxies to another provider, the child provider's enabled/disabled state should be + * taken into account in the parent's enabled/disabled state. For most providers, it is expected + * that they will be always enabled. */ - public final void reportLocation(Location location) { - try { - mLocationManager.reportLocation(location, false); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException", e); - } catch (Exception e) { - // never crash provider, might be running in a system process - Log.e(TAG, "Exception", e); + public void setEnabled(boolean enabled) { + synchronized (mBinder) { + if (mEnabled == enabled) { + return; + } + + mEnabled = enabled; + } + + ILocationProviderManager manager = mManager; + if (manager != null) { + try { + manager.onSetEnabled(mEnabled); + } catch (RemoteException | RuntimeException e) { + Log.w(mTag, e); + } } } /** - * Enable the location provider. - * <p>The provider may initialize resources, but does - * not yet need to report locations. + * Sets the provider properties that may be queried by clients. Generally speaking, providers + * should try to avoid changing their properties after construction. */ - public abstract void onEnable(); + public void setProperties(ProviderPropertiesUnbundled properties) { + synchronized (mBinder) { + mProperties = properties.getProviderProperties(); + } + + ILocationProviderManager manager = mManager; + if (manager != null) { + try { + manager.onSetProperties(mProperties); + } catch (RemoteException | RuntimeException e) { + Log.w(mTag, e); + } + } + } /** - * Disable the location provider. - * <p>The provider must release resources, and stop - * performing work. It may no longer report locations. + * Returns true if this provider has been set as enabled. This will be true unless explicitly + * set otherwise. */ - public abstract void onDisable(); + public boolean isEnabled() { + return mEnabled; + } /** - * Set the {@link ProviderRequest} requirements for this provider. - * <p>Each call to this method overrides all previous requests. - * <p>This method might trigger the provider to start returning - * locations, or to stop returning locations, depending on the - * parameters in the request. + * Reports a new location from this provider. */ - public abstract void onSetRequest(ProviderRequestUnbundled request, WorkSource source); + public void reportLocation(Location location) { + ILocationProviderManager manager = mManager; + if (manager != null) { + try { + manager.onReportLocation(location); + } catch (RemoteException | RuntimeException e) { + Log.w(mTag, e); + } + } + } + + protected void onInit() { + // call once so that providers designed for APIs pre-Q are not broken + onEnable(); + } + + /** + * @deprecated This callback will be invoked once when the provider is created to maintain + * backwards compatibility with providers not designed for Android Q and above. This method + * should only be implemented in location providers that need to support SDKs below Android Q. + * Even in this case, it is usually unnecessary to implement this callback with the correct + * design. This method may be removed in the future. + */ + @Deprecated + protected void onEnable() {} + + /** + * @deprecated This callback will be never be invoked on Android Q and above. This method should + * only be implemented in location providers that need to support SDKs below Android Q. Even in + * this case, it is usually unnecessary to implement this callback with the correct design. This + * method may be removed in the future. + */ + @Deprecated + protected void onDisable() {} + + /** + * Set the {@link ProviderRequest} requirements for this provider. Each call to this method + * overrides all previous requests. This method might trigger the provider to start returning + * locations, or to stop returning locations, depending on the parameters in the request. + */ + protected abstract void onSetRequest(ProviderRequestUnbundled request, WorkSource source); /** * Dump debug information. */ - public void onDump(FileDescriptor fd, PrintWriter pw, String[] args) { - } + protected void onDump(FileDescriptor fd, PrintWriter pw, String[] args) {} /** * This method will no longer be invoked. @@ -187,10 +231,12 @@ public abstract class LocationProviderBase { * <p>If extras is non-null, additional status information may be * added to it in the form of provider-specific key/value pairs. * - * @deprecated This method will no longer be invoked. + * @deprecated This callback will be never be invoked on Android Q and above. This method should + * only be implemented in location providers that need to support SDKs below Android Q. This + * method may be removed in the future. */ @Deprecated - public int onGetStatus(Bundle extras) { + protected int onGetStatus(Bundle extras) { return LocationProvider.AVAILABLE; } @@ -206,24 +252,64 @@ public abstract class LocationProviderBase { * * @return time of last status update in millis since last reboot * - * @deprecated This method will no longer be invoked. + * @deprecated This callback will be never be invoked on Android Q and above. This method should + * only be implemented in location providers that need to support SDKs below Android Q. This + * method may be removed in the future. */ @Deprecated - public long onGetStatusUpdateTime() { + protected long onGetStatusUpdateTime() { return 0; } /** - * Implements addditional location provider specific additional commands. - * - * @param command name of the command to send to the provider. - * @param extras optional arguments for the command (or null). - * The provider may optionally fill the extras Bundle with results from the command. - * - * @return true if the command succeeds. + * Implements location provider specific custom commands. The return value will be ignored on + * Android Q and above. */ - public boolean onSendExtraCommand(String command, Bundle extras) { - // default implementation + protected boolean onSendExtraCommand(@Nullable String command, @Nullable Bundle extras) { return false; } + + private final class Service extends ILocationProvider.Stub { + + @Override + public void setLocationProviderManager(ILocationProviderManager manager) { + synchronized (mBinder) { + try { + manager.onSetProperties(mProperties); + manager.onSetEnabled(mEnabled); + } catch (RemoteException e) { + Log.w(mTag, e); + } + + mManager = manager; + } + + onInit(); + } + + @Override + public void setRequest(ProviderRequest request, WorkSource ws) { + onSetRequest(new ProviderRequestUnbundled(request), ws); + } + + @Override + public int getStatus(Bundle extras) { + return onGetStatus(extras); + } + + @Override + public long getStatusUpdateTime() { + return onGetStatusUpdateTime(); + } + + @Override + public void sendExtraCommand(String command, Bundle extras) { + onSendExtraCommand(command, extras); + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + onDump(fd, pw, args); + } + } } diff --git a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java index 6a8e61877e46..b825b58cd3e9 100644 --- a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java +++ b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java @@ -16,13 +16,13 @@ package com.android.location.provider; -import java.util.ArrayList; -import java.util.List; - import android.location.LocationRequest; import com.android.internal.location.ProviderRequest; +import java.util.ArrayList; +import java.util.List; + /** * This class is an interface to Provider Requests for unbundled applications. * @@ -46,6 +46,10 @@ public final class ProviderRequestUnbundled { return mRequest.interval; } + public boolean getForceLocation() { + return mRequest.forceLocation; + } + /** * Never null. */ diff --git a/media/Android.bp b/media/Android.bp new file mode 100644 index 000000000000..d5da6f266952 --- /dev/null +++ b/media/Android.bp @@ -0,0 +1,36 @@ +java_library { + // TODO: include media2.jar in the media apex and add it to the bootclasspath. + name: "media2", + + srcs: [ + ":media2-srcs", + ":framework-media-annotation-srcs", + ], + + static_libs: [ + "mediaplayer2-protos", + ], + + // Make sure that the implementaion only relies on SDK or system APIs. + sdk_version: "system_current", +} + +filegroup { + name: "media2-srcs", + srcs: [ + "java/android/media/CloseGuard.java", + "java/android/media/DataSourceCallback.java", + "java/android/media/DataSourceDesc.java", + "java/android/media/UriDataSourceDesc.java", + "java/android/media/FileDataSourceDesc.java", + "java/android/media/CallbackDataSourceDesc.java", + "java/android/media/VideoSize.java", + "java/android/media/Media2Utils.java", + "java/android/media/MediaPlayer2Utils.java", + "java/android/media/MediaPlayer2.java", + "java/android/media/Media2HTTPService.java", + "java/android/media/Media2HTTPConnection.java", + "java/android/media/RoutingDelegate.java", + "java/android/media/BufferingParams.java", + ], +} diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java index 5467a69ea0bb..0a9ca025e2b0 100644 --- a/media/java/android/media/AudioFocusInfo.java +++ b/media/java/android/media/AudioFocusInfo.java @@ -80,16 +80,12 @@ public final class AudioFocusInfo implements Parcelable { * The audio attributes for the audio focus request. * @return non-null {@link AudioAttributes}. */ - @SystemApi public AudioAttributes getAttributes() { return mAttributes; } - @SystemApi public int getClientUid() { return mClientUid; } - @SystemApi public String getClientId() { return mClientId; } - @SystemApi public String getPackageName() { return mPackageName; } /** @@ -99,7 +95,6 @@ public final class AudioFocusInfo implements Parcelable { * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. */ - @SystemApi public int getGainRequest() { return mGainRequest; } /** @@ -109,7 +104,6 @@ public final class AudioFocusInfo implements Parcelable { * {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT} or * {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}. */ - @SystemApi public int getLossReceived() { return mLossReceived; } /** @hide */ @@ -124,7 +118,6 @@ public final class AudioFocusInfo implements Parcelable { * {@link AudioManager#AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}, and * {@link AudioManager#AUDIOFOCUS_FLAG_LOCK}. */ - @SystemApi public int getFlags() { return mFlags; } @Override diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index 3e3e65188761..793aa270e2fd 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -213,6 +213,8 @@ import java.util.Objects; * (e.g.{@link AudioTrack#getPlaybackHeadPosition() * AudioTrack.getPlaybackHeadPosition()}), * depending on the context where audio frame is used. + * For the purposes of {@link AudioFormat#getFrameSizeInBytes()}, a compressed data format + * returns a frame size of 1 byte. */ public final class AudioFormat implements Parcelable { @@ -670,27 +672,53 @@ public final class AudioFormat implements Parcelable { } /** - * Private constructor with an ignored argument to differentiate from the removed default ctor - * @param ignoredArgument - */ - private AudioFormat(int ignoredArgument) { - } - - /** * Constructor used by the JNI. Parameters are not checked for validity. */ // Update sound trigger JNI in core/jni/android_hardware_SoundTrigger.cpp when modifying this // constructor @UnsupportedAppUsage private AudioFormat(int encoding, int sampleRate, int channelMask, int channelIndexMask) { - mEncoding = encoding; - mSampleRate = sampleRate; - mChannelMask = channelMask; - mChannelIndexMask = channelIndexMask; - mPropertySetMask = AUDIO_FORMAT_HAS_PROPERTY_ENCODING | - AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE | - AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK | - AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK; + this( + AUDIO_FORMAT_HAS_PROPERTY_ENCODING + | AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE + | AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK + | AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK, + encoding, sampleRate, channelMask, channelIndexMask + ); + } + + private AudioFormat(int propertySetMask, + int encoding, int sampleRate, int channelMask, int channelIndexMask) { + mPropertySetMask = propertySetMask; + mEncoding = (propertySetMask & AUDIO_FORMAT_HAS_PROPERTY_ENCODING) != 0 + ? encoding : ENCODING_INVALID; + mSampleRate = (propertySetMask & AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE) != 0 + ? sampleRate : SAMPLE_RATE_UNSPECIFIED; + mChannelMask = (propertySetMask & AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK) != 0 + ? channelMask : CHANNEL_INVALID; + mChannelIndexMask = (propertySetMask & AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK) != 0 + ? channelIndexMask : CHANNEL_INVALID; + + // Compute derived values. + + final int channelIndexCount = Integer.bitCount(getChannelIndexMask()); + int channelCount = channelCountFromOutChannelMask(getChannelMask()); + if (channelCount == 0) { + channelCount = channelIndexCount; + } else if (channelCount != channelIndexCount && channelIndexCount != 0) { + channelCount = 0; // position and index channel count mismatch + } + mChannelCount = channelCount; + + int frameSizeInBytes = 1; + try { + frameSizeInBytes = getBytesPerSample(mEncoding) * channelCount; + } catch (IllegalArgumentException iae) { + // ignored + } + // it is possible that channel count is 0, so ensure we return 1 for + // mFrameSizeInBytes for consistency. + mFrameSizeInBytes = frameSizeInBytes != 0 ? frameSizeInBytes : 1; } /** @hide */ @@ -704,14 +732,21 @@ public final class AudioFormat implements Parcelable { /** @hide */ public final static int AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK = 0x1 << 3; + // This is an immutable class, all member variables are final. + + // Essential values. @UnsupportedAppUsage - private int mEncoding; + private final int mEncoding; @UnsupportedAppUsage - private int mSampleRate; + private final int mSampleRate; @UnsupportedAppUsage - private int mChannelMask; - private int mChannelIndexMask; - private int mPropertySetMask; + private final int mChannelMask; + private final int mChannelIndexMask; + private final int mPropertySetMask; + + // Derived values computed in the constructor, cached here. + private final int mChannelCount; + private final int mFrameSizeInBytes; /** * Return the encoding. @@ -721,9 +756,6 @@ public final class AudioFormat implements Parcelable { * {@link AudioFormat#ENCODING_INVALID} if not set. */ public int getEncoding() { - if ((mPropertySetMask & AUDIO_FORMAT_HAS_PROPERTY_ENCODING) == 0) { - return ENCODING_INVALID; - } return mEncoding; } @@ -745,9 +777,6 @@ public final class AudioFormat implements Parcelable { * {@link AudioFormat#CHANNEL_INVALID} if not set. */ public int getChannelMask() { - if ((mPropertySetMask & AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK) == 0) { - return CHANNEL_INVALID; - } return mChannelMask; } @@ -760,9 +789,6 @@ public final class AudioFormat implements Parcelable { * {@link AudioFormat#CHANNEL_INVALID} if not set or an invalid mask was used. */ public int getChannelIndexMask() { - if ((mPropertySetMask & AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK) == 0) { - return CHANNEL_INVALID; - } return mChannelIndexMask; } @@ -772,14 +798,26 @@ public final class AudioFormat implements Parcelable { * Zero is returned if both the channel position mask and the channel index mask are not set. */ public int getChannelCount() { - final int channelIndexCount = Integer.bitCount(getChannelIndexMask()); - int channelCount = channelCountFromOutChannelMask(getChannelMask()); - if (channelCount == 0) { - channelCount = channelIndexCount; - } else if (channelCount != channelIndexCount && channelIndexCount != 0) { - channelCount = 0; // position and index channel count mismatch - } - return channelCount; + return mChannelCount; + } + + /** + * Return the frame size in bytes. + * + * For PCM or PCM packed compressed data this is the size of a sample multiplied + * by the channel count. For all other cases, including invalid/unset channel masks, + * this will return 1 byte. + * As an example, a stereo 16-bit PCM format would have a frame size of 4 bytes, + * an 8 channel float PCM format would have a frame size of 32 bytes, + * and a compressed data format (not packed in PCM) would have a frame size of 1 byte. + * + * Both {@link AudioRecord} or {@link AudioTrack} process data in multiples of + * this frame size. + * + * @return The audio frame size in bytes corresponding to the encoding and the channel mask. + */ + public int getFrameSizeInBytes() { + return mFrameSizeInBytes; } /** @hide */ @@ -790,7 +828,7 @@ public final class AudioFormat implements Parcelable { /** @hide */ public String toLogFriendlyString() { return String.format("%dch %dHz %s", - getChannelCount(), mSampleRate, toLogFriendlyEncoding(mEncoding)); + mChannelCount, mSampleRate, toLogFriendlyEncoding(mEncoding)); } /** @@ -839,14 +877,13 @@ public final class AudioFormat implements Parcelable { * @return a new {@link AudioFormat} object */ public AudioFormat build() { - AudioFormat af = new AudioFormat(1980/*ignored*/); - af.mEncoding = mEncoding; - // not calling setSampleRate is equivalent to calling - // setSampleRate(SAMPLE_RATE_UNSPECIFIED) - af.mSampleRate = mSampleRate; - af.mChannelMask = mChannelMask; - af.mChannelIndexMask = mChannelIndexMask; - af.mPropertySetMask = mPropertySetMask; + AudioFormat af = new AudioFormat( + mPropertySetMask, + mEncoding, + mSampleRate, + mChannelMask, + mChannelIndexMask + ); return af; } @@ -1049,11 +1086,13 @@ public final class AudioFormat implements Parcelable { } private AudioFormat(Parcel in) { - mPropertySetMask = in.readInt(); - mEncoding = in.readInt(); - mSampleRate = in.readInt(); - mChannelMask = in.readInt(); - mChannelIndexMask = in.readInt(); + this( + in.readInt(), // propertySetMask + in.readInt(), // encoding + in.readInt(), // sampleRate + in.readInt(), // channelMask + in.readInt() // channelIndexMask + ); } public static final Parcelable.Creator<AudioFormat> CREATOR = diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index d10900e5d160..da52a268e384 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -5152,6 +5152,15 @@ public class AudioManager { return reportedSurroundFormats; } + /** + * Return if audio haptic coupled playback is supported or not. + * + * @return whether audio haptic playback supported. + */ + public static boolean isHapticPlaybackSupported() { + return AudioSystem.isHapticPlaybackSupported(); + } + //--------------------------------------------------------- // Inner classes diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java index 823af656abaa..fca70747e202 100644 --- a/media/java/android/media/AudioPresentation.java +++ b/media/java/android/media/AudioPresentation.java @@ -254,6 +254,22 @@ public final class AudioPresentation { mLabels.hashCode()); } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(getClass().getSimpleName() + " "); + sb.append("{ presentation id=" + mPresentationId); + sb.append(", program id=" + mProgramId); + sb.append(", language=" + mLanguage); + sb.append(", labels=" + mLabels); + sb.append(", mastering indication=" + mMasteringIndication); + sb.append(", audio description=" + mAudioDescriptionAvailable); + sb.append(", spoken subtitles=" + mSpokenSubtitlesAvailable); + sb.append(", dialogue enhancement=" + mDialogueEnhancementAvailable); + sb.append(" }"); + return sb.toString(); + } + /** * A builder class for creating {@link AudioPresentation} objects. */ diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 082a375470ee..36f635a8e572 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -933,6 +933,11 @@ public class AudioSystem */ public static native int setA11yServicesUids(int[] uids); + /** + * @see AudioManager#isHapticPlaybackSupported() + */ + public static native boolean isHapticPlaybackSupported(); + // Items shared with audio service /** diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 2c4ec3a1a0a6..5e77fdf0a121 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -995,6 +995,35 @@ public class AudioTrack extends PlayerBase } } + /** + * Returns whether direct playback of an audio format with the provided attributes is + * currently supported on the system. + * <p>Direct playback means that the audio stream is not resampled or downmixed + * by the framework. Checking for direct support can help the app select the representation + * of audio content that most closely matches the capabilities of the device and peripherials + * (e.g. A/V receiver) connected to it. Note that the provided stream can still be re-encoded + * or mixed with other streams, if needed. + * <p>Also note that this query only provides information about the support of an audio format. + * It does not indicate whether the resources necessary for the playback are available + * at that instant. + * @param format a non-null {@link AudioFormat} instance describing the format of + * the audio data. + * @param attributes a non-null {@link AudioAttributes} instance. + * @return true if the given audio format can be played directly. + */ + public static boolean isDirectPlaybackSupported(@NonNull AudioFormat format, + @NonNull AudioAttributes attributes) { + if (format == null) { + throw new IllegalArgumentException("Illegal null AudioFormat argument"); + } + if (attributes == null) { + throw new IllegalArgumentException("Illegal null AudioAttributes argument"); + } + return native_is_direct_output_supported(format.getEncoding(), format.getSampleRate(), + format.getChannelMask(), format.getChannelIndexMask(), + attributes.getContentType(), attributes.getUsage(), attributes.getFlags()); + } + // mask of all the positional channels supported, however the allowed combinations // are further restricted by the matching left/right rule and // AudioSystem.OUT_CHANNEL_COUNT_MAX @@ -3328,6 +3357,9 @@ public class AudioTrack extends PlayerBase // Native methods called from the Java side //-------------------- + private static native boolean native_is_direct_output_supported(int encoding, int sampleRate, + int channelMask, int channelIndexMask, int contentType, int usage, int flags); + // post-condition: mStreamType is overwritten with a value // that reflects the audio attributes (e.g. an AudioAttributes object with a usage of // AudioAttributes.USAGE_MEDIA will map to AudioManager.STREAM_MUSIC @@ -3365,7 +3397,7 @@ public class AudioTrack extends PlayerBase int offsetInFloats, int sizeInFloats, int format, boolean isBlocking); - private native final int native_write_native_bytes(Object audioData, + private native final int native_write_native_bytes(ByteBuffer audioData, int positionInBytes, int sizeInBytes, int format, boolean blocking); private native final int native_reload_static(); diff --git a/media/java/android/media/CallbackDataSourceDesc.java b/media/java/android/media/CallbackDataSourceDesc.java index cb9669bb653d..cd364145e8a4 100644 --- a/media/java/android/media/CallbackDataSourceDesc.java +++ b/media/java/android/media/CallbackDataSourceDesc.java @@ -21,10 +21,8 @@ import android.annotation.NonNull; /** * Structure of data source descriptor for sources using callback. * - * Used by {@link MediaPlayer2#setDataSource(DataSourceDesc)}, - * {@link MediaPlayer2#setNextDataSource(DataSourceDesc)} and - * {@link MediaPlayer2#setNextDataSources(List<DataSourceDesc>)} - * to set data source for playback. + * Used by {@link MediaPlayer2#setDataSource}, {@link MediaPlayer2#setNextDataSource} and + * {@link MediaPlayer2#setNextDataSources} to set data source for playback. * * <p>Users should use {@link Builder} to create {@link CallbackDataSourceDesc}. * @@ -37,7 +35,6 @@ public class CallbackDataSourceDesc extends DataSourceDesc { /** * Return the DataSourceCallback of this data source. - * It's meaningful only when {@code getType} returns {@link #TYPE_CALLBACK}. * @return the DataSourceCallback of this data source */ public DataSourceCallback getDataSourceCallback() { diff --git a/media/java/android/media/DataSourceDesc.java b/media/java/android/media/DataSourceDesc.java index 9109ea5bdab2..7fc6f794cff8 100644 --- a/media/java/android/media/DataSourceDesc.java +++ b/media/java/android/media/DataSourceDesc.java @@ -21,10 +21,8 @@ import android.annotation.NonNull; /** * Base class of data source descriptor. * - * Used by {@link MediaPlayer2#setDataSource(DataSourceDesc)}, - * {@link MediaPlayer2#setNextDataSource(DataSourceDesc)} and - * {@link MediaPlayer2#setNextDataSources(List<DataSourceDesc>)} - * to set data source for playback. + * Used by {@link MediaPlayer2#setDataSource}, {@link MediaPlayer2#setNextDataSource} and + * {@link MediaPlayer2#setNextDataSources} to set data source for playback. * * <p>Users should use subclasses' builder to change {@link DataSourceDesc}. * diff --git a/media/java/android/media/FileDataSourceDesc.java b/media/java/android/media/FileDataSourceDesc.java index 14ef18029233..aca8dbed1a13 100644 --- a/media/java/android/media/FileDataSourceDesc.java +++ b/media/java/android/media/FileDataSourceDesc.java @@ -25,10 +25,8 @@ import java.io.IOException; /** * Structure of data source descriptor for sources using file descriptor. * - * Used by {@link MediaPlayer2#setDataSource(DataSourceDesc)}, - * {@link MediaPlayer2#setNextDataSource(DataSourceDesc)} and - * {@link MediaPlayer2#setNextDataSources(List<DataSourceDesc>)} - * to set data source for playback. + * Used by {@link MediaPlayer2#setDataSource}, {@link MediaPlayer2#setNextDataSource} and + * {@link MediaPlayer2#setNextDataSources} to set data source for playback. * * <p>Users should use {@link Builder} to create {@link FileDataSourceDesc}. * @@ -165,9 +163,9 @@ public class FileDataSourceDesc extends DataSourceDesc { * Sets the data source (ParcelFileDescriptor) to use. The ParcelFileDescriptor must be * seekable (N.B. a LocalSocket is not seekable). When the {@link FileDataSourceDesc} * created by this builder is passed to {@link MediaPlayer2} via - * {@link MediaPlayer2#setDataSource(DataSourceDesc)}, - * {@link MediaPlayer2#setNextDataSource(DataSourceDesc)} or - * {@link MediaPlayer2#setNextDataSources(List<DataSourceDesc>)}, MediaPlayer2 will + * {@link MediaPlayer2#setDataSource}, + * {@link MediaPlayer2#setNextDataSource} or + * {@link MediaPlayer2#setNextDataSources}, MediaPlayer2 will * close the ParcelFileDescriptor. * * @param pfd the ParcelFileDescriptor for the file to play @@ -185,9 +183,9 @@ public class FileDataSourceDesc extends DataSourceDesc { * Sets the data source (ParcelFileDescriptor) to use. The ParcelFileDescriptor must be * seekable (N.B. a LocalSocket is not seekable). When the {@link FileDataSourceDesc} * created by this builder is passed to {@link MediaPlayer2} via - * {@link MediaPlayer2#setDataSource(DataSourceDesc)}, - * {@link MediaPlayer2#setNextDataSource(DataSourceDesc)} or - * {@link MediaPlayer2#setNextDataSources(List<DataSourceDesc>)}, MediaPlayer2 will + * {@link MediaPlayer2#setDataSource}, + * {@link MediaPlayer2#setNextDataSource} or + * {@link MediaPlayer2#setNextDataSources}, MediaPlayer2 will * close the ParcelFileDescriptor. * * Any negative number for offset is treated as 0. diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 1d27c03e1dcb..0375de3cc496 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -27,7 +27,6 @@ import android.media.MediaCodecInfo.CodecCapabilities; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.os.IBinder; import android.os.IHwBinder; import android.os.Looper; import android.os.Message; @@ -1427,6 +1426,24 @@ import java.util.concurrent.locks.ReentrantLock; <td>⎆</td> </tr> <tr> + <td>(29+)</td> + <td>29+</td> + <td>29+</td> + <td>29+</td> + <td>(29+)</td> + <td>(29+)</td> + <td>-</td> + <td class=fn>{@link #setAudioPresentation setAudioPresentation}</td> + <td></td> + <td></td> + <td></td> + <td></td> + <td></td> + <td></td> + <td></td> + <td></td> + </tr> + <tr> <td>-</td> <td>-</td> <td>18+</td> @@ -3260,6 +3277,19 @@ final public class MediaCodec { public native final void setVideoScalingMode(@VideoScalingMode int mode); /** + * Sets the audio presentation. + * @param presentation see {@link AudioPresentation}. In particular, id should be set. + */ + public void setAudioPresentation(@NonNull AudioPresentation presentation) { + if (presentation == null) { + throw new IllegalArgumentException("audio presentation is null"); + } + native_setAudioPresentation(presentation.getPresentationId(), presentation.getProgramId()); + } + + private native void native_setAudioPresentation(int presentationId, int programId); + + /** * Get the component name. If the codec was created by createDecoderByType * or createEncoderByType, what component is chosen is not known beforehand. * @throws IllegalStateException if in the Released state. @@ -3308,6 +3338,55 @@ final public class MediaCodec { public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync"; /** + * Set the HDR10+ metadata on the next queued input frame. + * + * Provide a byte array of data that's conforming to the + * user_data_registered_itu_t_t35() syntax of SEI message for ST 2094-40. + *<p> + * For decoders: + *<p> + * When a decoder is configured for one of the HDR10+ profiles that uses + * out-of-band metadata (such as {@link + * MediaCodecInfo.CodecProfileLevel#VP9Profile2HDR10Plus} or {@link + * MediaCodecInfo.CodecProfileLevel#VP9Profile3HDR10Plus}), this + * parameter sets the HDR10+ metadata on the next input buffer queued + * to the decoder. A decoder supporting these profiles must propagate + * the metadata to the format of the output buffer corresponding to this + * particular input buffer (under key {@link MediaFormat#KEY_HDR10_PLUS_INFO}). + * The metadata should be applied to that output buffer and the buffers + * following it (in display order), until the next output buffer (in + * display order) upon which an HDR10+ metadata is set. + *<p> + * This parameter shouldn't be set if the decoder is not configured for + * an HDR10+ profile that uses out-of-band metadata. In particular, + * it shouldn't be set for HDR10+ profiles that uses in-band metadata + * where the metadata is embedded in the input buffers, for example + * {@link MediaCodecInfo.CodecProfileLevel#HEVCProfileMain10HDR10Plus}. + *<p> + * For encoders: + *<p> + * When an encoder is configured for one of the HDR10+ profiles and the + * operates in byte buffer input mode (instead of surface input mode), + * this parameter sets the HDR10+ metadata on the next input buffer queued + * to the encoder. For the HDR10+ profiles that uses out-of-band metadata + * (such as {@link MediaCodecInfo.CodecProfileLevel#VP9Profile2HDR10Plus}, + * or {@link MediaCodecInfo.CodecProfileLevel#VP9Profile3HDR10Plus}), + * the metadata must be propagated to the format of the output buffer + * corresponding to this particular input buffer (under key {@link + * MediaFormat#KEY_HDR10_PLUS_INFO}). For the HDR10+ profiles that uses + * in-band metadata (such as {@link + * MediaCodecInfo.CodecProfileLevel#HEVCProfileMain10HDR10Plus}), the + * metadata info must be embedded in the corresponding output buffer itself. + *<p> + * This parameter shouldn't be set if the encoder is not configured for + * an HDR10+ profile, or if it's operating in surface input mode. + *<p> + * + * @see MediaFormat#KEY_HDR10_PLUS_INFO + */ + public static final String PARAMETER_KEY_HDR10_PLUS_INFO = MediaFormat.KEY_HDR10_PLUS_INFO; + + /** * Communicate additional parameter changes to the component instance. * <b>Note:</b> Some of these parameter changes may silently fail to apply. * @@ -3325,7 +3404,14 @@ final public class MediaCodec { int i = 0; for (final String key: params.keySet()) { keys[i] = key; - values[i] = params.get(key); + Object value = params.get(key); + + // Bundle's byte array is a byte[], JNI layer only takes ByteBuffer + if (value instanceof byte[]) { + values[i] = ByteBuffer.wrap((byte[])value); + } else { + values[i] = value; + } ++i; } diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 6301993a74be..902582905b0e 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -1135,6 +1135,10 @@ public final class MediaCodecInfo { maxChannels = 6; } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) { maxChannels = 16; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) { + sampleRates = new int[] { 48000 }; + bitRates = Range.create(32000, 6144000); + maxChannels = 16; } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) { sampleRates = new int[] { 44100, 48000, 96000, 192000 }; bitRates = Range.create(16000, 2688000); @@ -2516,6 +2520,8 @@ public final class MediaCodecInfo { case CodecProfileLevel.VP9Profile3: case CodecProfileLevel.VP9Profile2HDR: case CodecProfileLevel.VP9Profile3HDR: + case CodecProfileLevel.VP9Profile2HDR10Plus: + case CodecProfileLevel.VP9Profile3HDR10Plus: break; default: Log.w(TAG, "Unrecognized profile " @@ -2608,7 +2614,9 @@ public final class MediaCodecInfo { switch (profileLevel.profile) { case CodecProfileLevel.HEVCProfileMain: case CodecProfileLevel.HEVCProfileMain10: + case CodecProfileLevel.HEVCProfileMainStill: case CodecProfileLevel.HEVCProfileMain10HDR10: + case CodecProfileLevel.HEVCProfileMain10HDR10Plus: break; default: Log.w(TAG, "Unrecognized profile " @@ -2999,6 +3007,8 @@ public final class MediaCodecInfo { // HDR profiles also support passing HDR metadata public static final int VP9Profile2HDR = 0x1000; public static final int VP9Profile3HDR = 0x2000; + public static final int VP9Profile2HDR10Plus = 0x4000; + public static final int VP9Profile3HDR10Plus = 0x8000; // from OMX_VIDEO_VP9LEVELTYPE public static final int VP9Level1 = 0x1; @@ -3021,6 +3031,7 @@ public final class MediaCodecInfo { public static final int HEVCProfileMain10 = 0x02; public static final int HEVCProfileMainStill = 0x04; public static final int HEVCProfileMain10HDR10 = 0x1000; + public static final int HEVCProfileMain10HDR10Plus = 0x2000; // from OMX_VIDEO_HEVCLEVELTYPE public static final int HEVCMainTierLevel1 = 0x1; diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index b7743c9db4c0..594a22457cc3 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -147,6 +147,7 @@ public final class MediaFormat { public static final String MIMETYPE_AUDIO_MSGSM = "audio/gsm"; public static final String MIMETYPE_AUDIO_AC3 = "audio/ac3"; public static final String MIMETYPE_AUDIO_EAC3 = "audio/eac3"; + public static final String MIMETYPE_AUDIO_EAC3_JOC = "audio/eac3-joc"; public static final String MIMETYPE_AUDIO_AC4 = "audio/ac4"; public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled"; @@ -910,6 +911,27 @@ public final class MediaFormat { public static final String KEY_HDR_STATIC_INFO = "hdr-static-info"; /** + * An optional key describing the HDR10+ metadata of the video content. + * + * The associated value is a ByteBuffer containing HDR10+ metadata conforming to the + * user_data_registered_itu_t_t35() syntax of SEI message for ST 2094-40. This key will + * be present on: + *<p> + * - The formats of output buffers of a decoder configured for HDR10+ profiles (such as + * {@link MediaCodecInfo.CodecProfileLevel#VP9Profile2HDR10Plus}, {@link + * MediaCodecInfo.CodecProfileLevel#VP9Profile3HDR10Plus} or {@link + * MediaCodecInfo.CodecProfileLevel#HEVCProfileMain10HDR10Plus}), or + *<p> + * - The formats of output buffers of an encoder configured for an HDR10+ profiles that + * uses out-of-band metadata (such as {@link + * MediaCodecInfo.CodecProfileLevel#VP9Profile2HDR10Plus} or {@link + * MediaCodecInfo.CodecProfileLevel#VP9Profile3HDR10Plus}). + * + * @see MediaCodec#PARAMETER_KEY_HDR10_PLUS_INFO + */ + public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info"; + + /** * A key describing a unique ID for the content of a media track. * * <p>This key is used by {@link MediaExtractor}. Some extractors provide multiple encodings diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java index c91d4d3de442..0fb392bfc0fe 100644 --- a/media/java/android/media/MediaMuxer.java +++ b/media/java/android/media/MediaMuxer.java @@ -18,10 +18,10 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.media.MediaCodec; import android.media.MediaCodec.BufferInfo; + import dalvik.system.CloseGuard; import java.io.FileDescriptor; @@ -269,8 +269,10 @@ final public class MediaMuxer { public static final int MUXER_OUTPUT_3GPP = MUXER_OUTPUT_FIRST + 2; /** HEIF media file format*/ public static final int MUXER_OUTPUT_HEIF = MUXER_OUTPUT_FIRST + 3; + /** Ogg media file format*/ + public static final int MUXER_OUTPUT_OGG = MUXER_OUTPUT_FIRST + 4; /** @hide */ - public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_HEIF; + public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_OGG; }; /** @hide */ @@ -279,6 +281,7 @@ final public class MediaMuxer { OutputFormat.MUXER_OUTPUT_WEBM, OutputFormat.MUXER_OUTPUT_3GPP, OutputFormat.MUXER_OUTPUT_HEIF, + OutputFormat.MUXER_OUTPUT_OGG, }) @Retention(RetentionPolicy.SOURCE) public @interface Format {} diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java index e6ad4441401a..d4b1c7f868cb 100644 --- a/media/java/android/media/MediaPlayer2.java +++ b/media/java/android/media/MediaPlayer2.java @@ -98,11 +98,10 @@ import java.util.concurrent.atomic.AtomicLong; * <p>The MediaPlayer2 object has five states:</p> * <ol> * <li><p>{@link #PLAYER_STATE_IDLE}: MediaPlayer2 is in the <strong>Idle</strong> - * state after you create it using - * {@link #create()}, or after calling {@link #reset()}.</p> + * state after it's created, or after calling {@link #reset()}.</p> * * <p>While in this state, you should call - * {@link #setDataSource(DataSourceDesc2) setDataSource()}. It is a good + * {@link #setDataSource setDataSource}. It is a good * programming practice to register an {@link EventCallback#onCallCompleted onCallCompleted} * <a href="#Callbacks">callback</a> and watch for {@link #CALL_STATUS_BAD_VALUE} and * {@link #CALL_STATUS_ERROR_IO}, which might be caused by <code>setDataSource</code>. @@ -134,7 +133,7 @@ import java.util.concurrent.atomic.AtomicLong; * while streaming audio/video.</p> * * <p> When the playback reaches the end of stream, the behavior depends on whether or - * not you've enabled looping by calling {@link #loopCurrent(boolean) loopCurrent}:</p> + * not you've enabled looping by calling {@link #loopCurrent}:</p> * <ul> * <li>If the looping mode was set to <code>false</code>, the player will transfer * to the <strong>Paused</strong> state. If you registered an {@link EventCallback#onInfo @@ -161,15 +160,15 @@ import java.util.concurrent.atomic.AtomicLong; * <p>If you register an {@link EventCallback#onError onError}} * <a href="#Callbacks">callback</a>, * the callback will be performed when entering the state. When programming errors happen, - * such as calling {@link #prepare() prepare} and - * {@link #setDataSource(DataSourceDesc) setDataSource} methods + * such as calling {@link #prepare()} and + * {@link #setDataSource} methods * from an <a href="#InvalidStates">invalid state</a>, the callback is called with * {@link #CALL_STATUS_INVALID_OPERATION}. The MediaPlayer2 object enters the * <strong>Error</strong> state whether or not a callback exists. </p> * * <p>To recover from an error and reuse a MediaPlayer2 object that is in the <strong> * Error</strong> state, - * call {@link #reset() reset}. The object will return to the <strong>Idle</strong> + * call {@link #reset()}. The object will return to the <strong>Idle</strong> * state and all state information will be lost.</p> * </li> * </ol> @@ -180,26 +179,26 @@ import java.util.concurrent.atomic.AtomicLong; * * <li>Use <a href="#Callbacks">callbacks</a> to respond to state changes and errors.</li> * - * <li>When a MediaPlayer2 object is no longer being used, call {@link #close() close} as soon as + * <li>When a MediaPlayer2 object is no longer being used, call {@link #close()} as soon as * possible to release the resources used by the internal player engine associated with the - * MediaPlayer2. Failure to call {@link #close() close} may cause subsequent instances of + * MediaPlayer2. Failure to call {@link #close()} may cause subsequent instances of * MediaPlayer2 objects to fallback to software implementations or fail altogether. * You cannot use MediaPlayer2 - * after you call {@link #close() close}. There is no way to bring it back to any other state.</li> + * after you call {@link #close()}. There is no way to bring it back to any other state.</li> * * <li>The current playback position can be retrieved with a call to - * {@link #getCurrentPosition() getCurrentPosition}, + * {@link #getCurrentPosition()}, * which is helpful for applications such as a Music player that need to keep track of the playback * progress.</li> * - * <li>The playback position can be adjusted with a call to {@link #seekTo seekTo}. Although the - * asynchronous {@link #seekTo seekTo} call returns right away, the actual seek operation may take a + * <li>The playback position can be adjusted with a call to {@link #seekTo}. Although the + * asynchronous {@link #seekTo} call returns right away, the actual seek operation may take a * while to finish, especially for audio/video being streamed. If you register an * {@link EventCallback#onCallCompleted onCallCompleted} <a href="#Callbacks">callback</a>, * the callback is * called When the seek operation completes with {@link #CALL_COMPLETED_SEEK_TO}.</li> * - * <li>You can call {@link #seekTo seekTo} from the <strong>Paused</strong> state. + * <li>You can call {@link #seekTo} from the <strong>Paused</strong> state. * In this case, if you are playing a video stream and * the requested position is valid one video frame is displayed.</li> * @@ -208,13 +207,13 @@ import java.util.concurrent.atomic.AtomicLong; * <h3 id="InvalidStates">Invalid method calls</h3> * * <p>The only methods you safely call from the <strong>Error</strong> state are - * {@link #close() close}, - * {@link #reset() reset}, - * {@link #notifyWhenCommandLabelReached notifyWhenCommandLabelReached}, - * {@link #clearPendingCommands() clearPendingCommands}, - * {@link #setEventCallback setEventCallback}, - * {@link #clearEventCallback() clearEventCallback} - * and {@link #getState() getState}. + * {@link #close}, + * {@link #reset}, + * {@link #notifyWhenCommandLabelReached}, + * {@link #clearPendingCommands}, + * {@link #registerEventCallback}, + * {@link #unregisterEventCallback} + * and {@link #getState}. * Any other methods might throw an exception, return meaningless data, or invoke a * {@link EventCallback#onCallCompleted onCallCompleted} with an error code.</p> * @@ -248,8 +247,7 @@ import java.util.concurrent.atomic.AtomicLong; * <h3 id="Callbacks">Callbacks</h3> * <p>Many errors do not result in a transition to the <strong>Error</strong> state. * It is good programming practice to register callback listeners using - * {@link #setEventCallback(Executor, EventCallback) setEventCallback} and - * {@link #setDrmEventCallback(Executor, DrmEventCallback) setDrmEventCallback}). + * {@link #registerEventCallback}. * You can receive a callback at any time and from any state.</p> * * <p>If it's important for your app to respond to state changes (for instance, to update the @@ -1445,7 +1443,7 @@ public class MediaPlayer2 implements AutoCloseable * @return the size of the video. The width and height of size could be 0 if there is no video, * no display surface was set, or the size has not been determined yet. * The {@code EventCallback} can be registered via - * {@link #setEventCallback(Executor, EventCallback)} to provide a + * {@link #registerEventCallback(Executor, EventCallback)} to provide a * notification {@code EventCallback.onVideoSizeChanged} when the size * is available. */ @@ -1713,10 +1711,12 @@ public class MediaPlayer2 implements AutoCloseable public MediaTimestamp getTimestamp() { try { // TODO: get the timestamp from native side - return new MediaTimestamp( - getCurrentPosition() * 1000L, - System.nanoTime(), - getState() == PLAYER_STATE_PLAYING ? getPlaybackParams().getSpeed() : 0.f); + return new MediaTimestamp.Builder() + .setMediaTimestamp( + getCurrentPosition() * 1000L, + System.nanoTime(), + getState() == PLAYER_STATE_PLAYING ? getPlaybackParams().getSpeed() : 0.f) + .build(); } catch (IllegalStateException e) { return null; } @@ -2400,11 +2400,13 @@ public class MediaPlayer2 implements AutoCloseable return; } Iterator<Value> in = playerMsg.getValuesList().iterator(); - SubtitleData data = new SubtitleData( - in.next().getInt32Value(), // trackIndex - in.next().getInt64Value(), // startTimeUs - in.next().getInt64Value(), // durationUs - in.next().getBytesValue().toByteArray()); // data + SubtitleData data = new SubtitleData.Builder() + .setSubtitleData( + in.next().getInt32Value(), // trackIndex + in.next().getInt64Value(), // startTimeUs + in.next().getInt64Value(), // durationUs + in.next().getBytesValue().toByteArray()) // data + .build(); sendEvent(new EventNotifier() { @Override public void notify(EventCallback callback) { @@ -2428,9 +2430,11 @@ public class MediaPlayer2 implements AutoCloseable return; } Iterator<Value> in = playerMsg.getValuesList().iterator(); - data = new TimedMetaData( - in.next().getInt64Value(), // timestampUs - in.next().getBytesValue().toByteArray()); // metaData + data = new TimedMetaData.Builder() + .setTimedMetaData( + in.next().getInt64Value(), // timestampUs + in.next().getBytesValue().toByteArray()) // metaData + .build(); } else { data = null; } @@ -3060,7 +3064,7 @@ public class MediaPlayer2 implements AutoCloseable public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED = SEPARATE_CALL_COMPLETED_CALLBACK_START; - /** The player just completed a call {@link #prepareDrm(DataSourceDesc, UUID)}. + /** The player just completed a call {@link #prepareDrm}. * @see DrmEventCallback#onDrmPrepared * @hide */ @@ -3138,9 +3142,10 @@ public class MediaPlayer2 implements AutoCloseable public static final int CALL_STATUS_SKIPPED = 5; /** Status code represents that DRM operation is called before preparing a DRM scheme through - * {@link #prepareDrm(DataSourceDesc, UUID)}. + * {@code prepareDrm}. * @see EventCallback#onCallCompleted */ + // TODO: change @code to @link when DRM is unhidden public static final int CALL_STATUS_NO_DRM_SCHEME = 6; /** @@ -3186,7 +3191,7 @@ public class MediaPlayer2 implements AutoCloseable * Register a callback to be invoked for configuration of the DRM object before * the session is created. * The callback will be invoked synchronously during the execution - * of {@link #prepareDrm(DataSourceDesc, UUID)}. + * of {@link #prepareDrm}. * * @param listener the callback that will be run * @hide @@ -3955,7 +3960,12 @@ public class MediaPlayer2 implements AutoCloseable textBounds = new Rect(left, top, right, bottom); } } + return null; + /* TimedText c-tor usage is temporarily commented out. + * TODO(b/117527789): use SUBTITLE path for MEDIA_MIMETYPE_TEXT_3GPP track + * and remove TimedText path from MediaPlayer2. return new TimedText(textChars, textBounds); + */ } } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index d4bfd6175a09..8ced021b1025 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -22,17 +22,17 @@ import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.hardware.Camera; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.view.Surface; +import com.android.internal.annotations.GuardedBy; + import java.io.File; import java.io.FileDescriptor; import java.io.IOException; @@ -41,8 +41,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; -import com.android.internal.annotations.GuardedBy; - /** * Used to record audio and video. The recording control is based on a * simple state machine (see below). @@ -450,6 +448,9 @@ public class MediaRecorder implements AudioRouting /** VP8/VORBIS data in a WEBM container */ public static final int WEBM = 9; + + /** Opus data in a Ogg container */ + public static final int OGG = 11; }; /** @@ -474,6 +475,8 @@ public class MediaRecorder implements AudioRouting public static final int AAC_ELD = 5; /** Ogg Vorbis audio codec */ public static final int VORBIS = 6; + /** Opus audio codec */ + public static final int OPUS = 7; } /** diff --git a/media/java/android/media/MediaTimestamp.java b/media/java/android/media/MediaTimestamp.java index e079a8e3a22b..03e454c1e962 100644 --- a/media/java/android/media/MediaTimestamp.java +++ b/media/java/android/media/MediaTimestamp.java @@ -16,6 +16,9 @@ package android.media; +import android.annotation.NonNull; +import android.annotation.SystemApi; + /** * An immutable object that represents the linear correlation between the media time * and the system time. It contains the media clock rate, together with the media timestamp @@ -117,4 +120,71 @@ public final class MediaTimestamp + " clockRate=" + clockRate + "}"; } + + /** + * Builder class for {@link MediaTimestamp} objects. + * <p> Here is an example where <code>Builder</code> is used to define the + * {@link MediaTimestamp}: + * + * <pre class="prettyprint"> + * MediaTimestamp mts = new MediaTimestamp.Builder() + * .setMediaTimestamp(mediaTime, systemTime, rate) + * .build(); + * </pre> + * @hide + */ + @SystemApi + public static class Builder { + long mMediaTimeUs; + long mNanoTime; + float mClockRate = 1.0f; + + /** + * Constructs a new Builder with the defaults. + */ + public Builder() { + } + + /** + * Constructs a new Builder from a given {@link MediaTimestamp} instance + * @param mts the {@link MediaTimestamp} object whose data will be reused + * in the new Builder. + */ + public Builder(@NonNull MediaTimestamp mts) { + if (mts == null) { + throw new IllegalArgumentException("null MediaTimestamp is not allowed"); + } + mMediaTimeUs = mts.mediaTimeUs; + mNanoTime = mts.nanoTime; + mClockRate = mts.clockRate; + } + + /** + * Combines all of the fields that have been set and return a new + * {@link MediaTimestamp} object. + * + * @return a new {@link MediaTimestamp} object + */ + public @NonNull MediaTimestamp build() { + return new MediaTimestamp(mMediaTimeUs, mNanoTime, mClockRate); + } + + /** + * Sets the info of media timestamp. + * + * @param mediaTimeUs the media time of the anchor in microseconds + * @param nanoTime the {@link java.lang.System#nanoTime system time} corresponding to + * the media time in nanoseconds. + * @param clockRate the rate of the media clock in relation to the system time. + * @return the same Builder instance. + */ + public @NonNull Builder setMediaTimestamp( + long mediaTimeUs, long nanoTime, float clockRate) { + mMediaTimeUs = mediaTimeUs; + mNanoTime = nanoTime; + mClockRate = clockRate; + + return this; + } + } } diff --git a/media/java/android/media/TimedMetaData.java b/media/java/android/media/TimedMetaData.java index bcc18ef92f94..2a8988855168 100644 --- a/media/java/android/media/TimedMetaData.java +++ b/media/java/android/media/TimedMetaData.java @@ -148,7 +148,7 @@ public final class TimedMetaData { * It should not be null. * @return the same Builder instance. */ - public @NonNull Builder setTimedMetaData(int timestamp, @NonNull byte[] metaData) { + public @NonNull Builder setTimedMetaData(long timestamp, @NonNull byte[] metaData) { if (metaData == null) { throw new IllegalArgumentException("null metaData is not allowed"); } diff --git a/media/java/android/media/UriDataSourceDesc.java b/media/java/android/media/UriDataSourceDesc.java index e39f53c9e19b..4eb9e8d3376f 100644 --- a/media/java/android/media/UriDataSourceDesc.java +++ b/media/java/android/media/UriDataSourceDesc.java @@ -32,10 +32,8 @@ import java.util.Map; /** * Structure of data source descriptor for sources using URI. * - * Used by {@link MediaPlayer2#setDataSource(DataSourceDesc)}, - * {@link MediaPlayer2#setNextDataSource(DataSourceDesc)} and - * {@link MediaPlayer2#setNextDataSources(List<DataSourceDesc>)} - * to set data source for playback. + * Used by {@link MediaPlayer2#setDataSource}, {@link MediaPlayer2#setNextDataSource} and + * {@link MediaPlayer2#setNextDataSources} to set data source for playback. * * <p>Users should use {@link Builder} to change {@link UriDataSourceDesc}. * diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java index 8bc1d35fef36..7fb3aa6d79f9 100644 --- a/media/java/android/media/audiopolicy/AudioMix.java +++ b/media/java/android/media/audiopolicy/AudioMix.java @@ -81,14 +81,12 @@ public class AudioMix { * An audio mix behavior where the output of the mix is sent to the original destination of * the audio signal, i.e. an output device for an output mix, or a recording for an input mix. */ - @SystemApi public static final int ROUTE_FLAG_RENDER = 0x1; /** * An audio mix behavior where the output of the mix is rerouted back to the framework and * is accessible for injection or capture through the {@link AudioTrack} and {@link AudioRecord} * APIs. */ - @SystemApi public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1; private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK; @@ -113,31 +111,23 @@ public class AudioMix { // MIX_STATE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h /** - * @hide * State of a mix before its policy is enabled. */ - @SystemApi public static final int MIX_STATE_DISABLED = -1; /** - * @hide * State of a mix when there is no audio to mix. */ - @SystemApi public static final int MIX_STATE_IDLE = 0; /** - * @hide * State of a mix that is actively mixing audio. */ - @SystemApi public static final int MIX_STATE_MIXING = 1; /** - * @hide * The current mixing state. * @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE}, * {@link #MIX_STATE_MIXING}. */ - @SystemApi public int getMixState() { return mMixState; } @@ -201,9 +191,7 @@ public class AudioMix { /** * Builder class for {@link AudioMix} objects - * */ - @SystemApi public static class Builder { private AudioMixingRule mRule = null; private AudioFormat mFormat = null; @@ -224,7 +212,6 @@ public class AudioMix { * @param rule a non-null {@link AudioMixingRule} instance. * @throws IllegalArgumentException */ - @SystemApi public Builder(AudioMixingRule rule) throws IllegalArgumentException { if (rule == null) { @@ -284,7 +271,6 @@ public class AudioMix { * @return the same Builder instance. * @throws IllegalArgumentException */ - @SystemApi public Builder setFormat(AudioFormat format) throws IllegalArgumentException { if (format == null) { @@ -302,7 +288,6 @@ public class AudioMix { * @return the same Builder instance. * @throws IllegalArgumentException */ - @SystemApi public Builder setRouteFlags(@RouteFlags int routeFlags) throws IllegalArgumentException { if (routeFlags == 0) { @@ -329,7 +314,6 @@ public class AudioMix { * @return the same Builder instance * @throws IllegalArgumentException */ - @SystemApi public Builder setDevice(@NonNull AudioDeviceInfo device) throws IllegalArgumentException { if (device == null) { throw new IllegalArgumentException("Illegal null AudioDeviceInfo argument"); @@ -347,7 +331,6 @@ public class AudioMix { * @return a new {@link AudioMix} object * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set. */ - @SystemApi public AudioMix build() throws IllegalArgumentException { if (mRule == null) { throw new IllegalArgumentException("Illegal null AudioMixingRule"); diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java index fbee62a86dcd..6c48cdb7b643 100644 --- a/media/java/android/media/audiopolicy/AudioMixingRule.java +++ b/media/java/android/media/audiopolicy/AudioMixingRule.java @@ -54,7 +54,6 @@ public class AudioMixingRule { * {@link Builder#addMixRule(int, Object)} where the Object parameter is an instance of * {@link AudioAttributes}. */ - @SystemApi public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1; /** * A rule requiring the capture preset information of the {@link AudioAttributes} to match. @@ -62,14 +61,12 @@ public class AudioMixingRule { * {@link Builder#addMixRule(int, Object)} where the Object parameter is an instance of * {@link AudioAttributes}. */ - @SystemApi public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1; /** * A rule requiring the UID of the audio stream to match that specified. * This mixing rule can be added with {@link Builder#addMixRule(int, Object)} where the Object * parameter is an instance of {@link java.lang.Integer}. */ - @SystemApi public static final int RULE_MATCH_UID = 0x1 << 2; private final static int RULE_EXCLUSION_MASK = 0x8000; @@ -239,7 +236,6 @@ public class AudioMixingRule { /** * Builder class for {@link AudioMixingRule} objects */ - @SystemApi public static class Builder { private ArrayList<AudioMixMatchCriterion> mCriteria; private int mTargetMixType = AudioMix.MIX_TYPE_INVALID; @@ -247,7 +243,6 @@ public class AudioMixingRule { /** * Constructs a new Builder with no rules. */ - @SystemApi public Builder() { mCriteria = new ArrayList<AudioMixMatchCriterion>(); } @@ -262,7 +257,6 @@ public class AudioMixingRule { * @throws IllegalArgumentException * @see #excludeRule(AudioAttributes, int) */ - @SystemApi public Builder addRule(AudioAttributes attrToMatch, int rule) throws IllegalArgumentException { if (!isValidAttributesSystemApiRule(rule)) { @@ -291,7 +285,6 @@ public class AudioMixingRule { * @throws IllegalArgumentException * @see #addRule(AudioAttributes, int) */ - @SystemApi public Builder excludeRule(AudioAttributes attrToMatch, int rule) throws IllegalArgumentException { if (!isValidAttributesSystemApiRule(rule)) { @@ -313,7 +306,6 @@ public class AudioMixingRule { * @throws IllegalArgumentException * @see #excludeMixRule(int, Object) */ - @SystemApi public Builder addMixRule(int rule, Object property) throws IllegalArgumentException { if (!isValidSystemApiRule(rule)) { throw new IllegalArgumentException("Illegal rule value " + rule); @@ -343,7 +335,6 @@ public class AudioMixingRule { * @return the same Builder instance. * @throws IllegalArgumentException */ - @SystemApi public Builder excludeMixRule(int rule, Object property) throws IllegalArgumentException { if (!isValidSystemApiRule(rule)) { throw new IllegalArgumentException("Illegal rule value " + rule); diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index 11107e2dd420..6103f55745f9 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -58,12 +58,10 @@ public class AudioPolicy { /** * The status of an audio policy that is valid but cannot be used because it is not registered. */ - @SystemApi public static final int POLICY_STATUS_UNREGISTERED = 1; /** * The status of an audio policy that is valid, successfully registered and thus active. */ - @SystemApi public static final int POLICY_STATUS_REGISTERED = 2; private int mStatus; @@ -75,7 +73,6 @@ public class AudioPolicy { * The behavior of a policy with regards to audio focus where it relies on the application * to do the ducking, the is the legacy and default behavior. */ - @SystemApi public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP; /** @@ -85,7 +82,6 @@ public class AudioPolicy { * <br>Can only be used after having set a listener with * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}. */ - @SystemApi public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; private AudioPolicyFocusListener mFocusListener; @@ -133,7 +129,6 @@ public class AudioPolicy { * Builder class for {@link AudioPolicy} objects. * By default the policy to be created doesn't govern audio focus decisions. */ - @SystemApi public static class Builder { private ArrayList<AudioMix> mMixes; private Context mContext; @@ -147,7 +142,6 @@ public class AudioPolicy { * Constructs a new Builder with no audio mixes. * @param context the context for the policy */ - @SystemApi public Builder(Context context) { mMixes = new ArrayList<AudioMix>(); mContext = context; @@ -159,7 +153,6 @@ public class AudioPolicy { * @return the same Builder instance. * @throws IllegalArgumentException */ - @SystemApi public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException { if (mix == null) { throw new IllegalArgumentException("Illegal null AudioMix argument"); @@ -174,7 +167,6 @@ public class AudioPolicy { * @return the same Builder instance. * @throws IllegalArgumentException */ - @SystemApi public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException { if (looper == null) { throw new IllegalArgumentException("Illegal null Looper argument"); @@ -187,7 +179,6 @@ public class AudioPolicy { * Sets the audio focus listener for the policy. * @param l a {@link AudioPolicy.AudioPolicyFocusListener} */ - @SystemApi public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) { mFocusListener = l; } @@ -201,7 +192,6 @@ public class AudioPolicy { * @param enforce true if the policy will govern audio focus decisions. * @return the same Builder instance. */ - @SystemApi public Builder setIsAudioFocusPolicy(boolean isFocusPolicy) { mIsFocusPolicy = isFocusPolicy; return this; @@ -211,12 +201,10 @@ public class AudioPolicy { * Sets the audio policy status listener. * @param l a {@link AudioPolicy.AudioPolicyStatusListener} */ - @SystemApi public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) { mStatusListener = l; } - @SystemApi /** * Sets the callback to receive all volume key-related events. * The callback will only be called if the device is configured to handle volume events @@ -240,7 +228,6 @@ public class AudioPolicy { * {@link AudioPolicy.AudioPolicyStatusListener} but the policy was configured * as an audio focus policy with {@link #setIsAudioFocusPolicy(boolean)}. */ - @SystemApi public AudioPolicy build() { if (mStatusListener != null) { // the AudioPolicy status listener includes updates on each mix activity state @@ -258,7 +245,6 @@ public class AudioPolicy { } /** - * @hide * Update the current configuration of the set of audio mixes by adding new ones, while * keeping the policy registered. * This method can only be called on a registered policy. @@ -266,7 +252,6 @@ public class AudioPolicy { * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR} * otherwise. */ - @SystemApi public int attachMixes(@NonNull List<AudioMix> mixes) { if (mixes == null) { throw new IllegalArgumentException("Illegal null list of AudioMix"); @@ -299,7 +284,6 @@ public class AudioPolicy { } /** - * @hide * Update the current configuration of the set of audio mixes by removing some, while * keeping the policy registered. * This method can only be called on a registered policy. @@ -307,7 +291,6 @@ public class AudioPolicy { * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR} * otherwise. */ - @SystemApi public int detachMixes(@NonNull List<AudioMix> mixes) { if (mixes == null) { throw new IllegalArgumentException("Illegal null list of AudioMix"); @@ -405,7 +388,6 @@ public class AudioPolicy { * Returns the current behavior for audio focus-related ducking. * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY} */ - @SystemApi public int getFocusDuckingBehavior() { return mConfig.mDuckingPolicy; } @@ -422,7 +404,6 @@ public class AudioPolicy { * @throws IllegalArgumentException * @throws IllegalStateException */ - @SystemApi public int setFocusDuckingBehavior(int behavior) throws IllegalArgumentException, IllegalStateException { if ((behavior != FOCUS_POLICY_DUCKING_IN_APP) @@ -466,7 +447,6 @@ public class AudioPolicy { * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. * @throws IllegalArgumentException */ - @SystemApi public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException { if (!policyReadyToUse()) { Log.e(TAG, "Cannot create AudioRecord sink for AudioMix"); @@ -506,7 +486,6 @@ public class AudioPolicy { * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. * @throws IllegalArgumentException */ - @SystemApi public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException { if (!policyReadyToUse()) { Log.e(TAG, "Cannot create AudioTrack source for AudioMix"); @@ -528,18 +507,15 @@ public class AudioPolicy { return at; } - @SystemApi public int getStatus() { return mStatus; } - @SystemApi public static abstract class AudioPolicyStatusListener { public void onStatusChange() {} public void onMixStateUpdate(AudioMix mix) {} } - @SystemApi public static abstract class AudioPolicyFocusListener { public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {} public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {} @@ -563,7 +539,6 @@ public class AudioPolicy { public void onAudioFocusAbandon(AudioFocusInfo afi) {} } - @SystemApi /** * Callback class to receive volume change-related events. * See {@link #Builder.setAudioPolicyVolumeCallback(AudioPolicyCallback)} to configure the diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index d91cf8732674..6a06dd07d22f 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -96,15 +96,9 @@ public final class MediaSessionManager { * @return The binder object from the system * @hide */ - @SystemApi public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub, - @NonNull String tag, int userId) { - try { - return mService.createSession(mContext.getPackageName(), cbStub, tag, userId); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return null; + @NonNull String tag, int userId) throws RemoteException { + return mService.createSession(mContext.getPackageName(), cbStub, tag, userId); } /** @@ -626,12 +620,24 @@ public final class MediaSessionManager { private final int mUid; private final IBinder mCallerBinder; + /** + * Create a new remote user information. + * + * @param packageName The package name of the remote user + * @param pid The pid of the remote user + * @param uid The uid of the remote user + */ public RemoteUserInfo(@NonNull String packageName, int pid, int uid) { this(packageName, pid, uid, null); } /** - * @hide + * Create a new remote user information. + * + * @param packageName The package name of the remote user + * @param pid The pid of the remote user + * @param uid The uid of the remote user + * @param callerBinder The binder of the remote user. Can be {@code null}. */ public RemoteUserInfo(String packageName, int pid, int uid, IBinder callerBinder) { mPackageName = packageName; diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index ec2d9bab5107..e8f188c7a27c 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -1332,7 +1332,6 @@ public final class TvInputManager { * * @return the list of content ratings blocked by the user. */ - @SystemApi public List<TvContentRating> getBlockedRatings() { try { List<TvContentRating> ratings = new ArrayList<>(); diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 503720939113..257860890437 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -752,6 +752,13 @@ void JMediaCodec::setVideoScalingMode(int mode) { } } +void JMediaCodec::selectAudioPresentation(const int32_t presentationId, const int32_t programId) { + sp<AMessage> msg = new AMessage; + msg->setInt32("audio-presentation-presentation-id", presentationId); + msg->setInt32("audio-presentation-program-id", programId); + (void)mCodec->setParameters(msg); +} + static jthrowable createCodecException( JNIEnv *env, status_t err, int32_t actionCode, const char *msg = NULL) { ScopedLocalRef<jclass> clazz( @@ -1874,6 +1881,18 @@ static void android_media_MediaCodec_setVideoScalingMode( codec->setVideoScalingMode(mode); } +static void android_media_MediaCodec_setAudioPresentation( + JNIEnv *env, jobject thiz, jint presentationId, jint programId) { + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; + } + + codec->selectAudioPresentation((int32_t)presentationId, (int32_t)programId); +} + static void android_media_MediaCodec_native_init(JNIEnv *env) { ScopedLocalRef<jclass> clazz( env, env->FindClass("android/media/MediaCodec")); @@ -2183,6 +2202,9 @@ static const JNINativeMethod gMethods[] = { { "setVideoScalingMode", "(I)V", (void *)android_media_MediaCodec_setVideoScalingMode }, + { "native_setAudioPresentation", "(II)V", + (void *)android_media_MediaCodec_setAudioPresentation }, + { "native_init", "()V", (void *)android_media_MediaCodec_native_init }, { "native_setup", "(Ljava/lang/String;ZZ)V", diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 985f55a89254..0a53f1a0e268 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -128,6 +128,8 @@ struct JMediaCodec : public AHandler { void setVideoScalingMode(int mode); + void selectAudioPresentation(const int32_t presentationId, const int32_t programId); + protected: virtual ~JMediaCodec(); diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp index 29238d3b8ea4..f3442f4b4d33 100644 --- a/media/jni/android_media_MediaExtractor.cpp +++ b/media/jni/android_media_MediaExtractor.cpp @@ -500,17 +500,17 @@ static jlong android_media_MediaExtractor_getSampleTime( if (extractor == NULL) { jniThrowException(env, "java/lang/IllegalStateException", NULL); - return -1ll; + return -1LL; } int64_t sampleTimeUs; status_t err = extractor->getSampleTime(&sampleTimeUs); if (err == ERROR_END_OF_STREAM) { - return -1ll; + return -1LL; } else if (err != OK) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return -1ll; + return -1LL; } return (jlong) sampleTimeUs; @@ -522,17 +522,17 @@ static jlong android_media_MediaExtractor_getSampleSize( if (extractor == NULL) { jniThrowException(env, "java/lang/IllegalStateException", NULL); - return -1ll; + return -1LL; } size_t sampleSize; status_t err = extractor->getSampleSize(&sampleSize); if (err == ERROR_END_OF_STREAM) { - return -1ll; + return -1LL; } else if (err != OK) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return -1ll; + return -1LL; } return (jlong) sampleSize; @@ -858,13 +858,13 @@ static jlong android_media_MediaExtractor_getCachedDurationUs( if (extractor == NULL) { jniThrowException(env, "java/lang/IllegalStateException", NULL); - return -1ll; + return -1LL; } int64_t cachedDurationUs; bool eos; if (!extractor->getCachedDuration(&cachedDurationUs, &eos)) { - return -1ll; + return -1LL; } return (jlong) cachedDurationUs; diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp index b7d7b0339376..45de36ed3e4d 100644 --- a/media/jni/audioeffect/android_media_Visualizer.cpp +++ b/media/jni/audioeffect/android_media_Visualizer.cpp @@ -440,6 +440,7 @@ static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) if (lpVisualizer == 0) { return; } + lpVisualizer->release(); } // delete the JNI data VisualizerJniStorage* lpJniStorage = diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp index ed2afdd84a7c..5f513207bd5f 100644 --- a/media/jni/soundpool/SoundPool.cpp +++ b/media/jni/soundpool/SoundPool.cpp @@ -513,7 +513,8 @@ Sample::~Sample() static status_t decode(int fd, int64_t offset, int64_t length, uint32_t *rate, int *numChannels, audio_format_t *audioFormat, - sp<MemoryHeapBase> heap, size_t *memsize) { + audio_channel_mask_t *channelMask, sp<MemoryHeapBase> heap, + size_t *memsize) { ALOGV("fd %d, offset %" PRId64 ", size %" PRId64, fd, offset, length); AMediaExtractor *ex = AMediaExtractor_new(); @@ -650,6 +651,10 @@ static status_t decode(int fd, int64_t offset, int64_t length, (void)AMediaFormat_delete(format); return UNKNOWN_ERROR; } + if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_MASK, + (int32_t*) channelMask)) { + *channelMask = AUDIO_CHANNEL_NONE; + } (void)AMediaFormat_delete(format); *memsize = written; return OK; @@ -665,12 +670,13 @@ status_t Sample::doLoad() uint32_t sampleRate; int numChannels; audio_format_t format; + audio_channel_mask_t channelMask; status_t status; mHeap = new MemoryHeapBase(kDefaultHeapSize); ALOGV("Start decode"); status = decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format, - mHeap, &mSize); + &channelMask, mHeap, &mSize); ALOGV("close(%d)", mFd); ::close(mFd); mFd = -1; @@ -697,6 +703,7 @@ status_t Sample::doLoad() mSampleRate = sampleRate; mNumChannels = numChannels; mFormat = format; + mChannelMask = channelMask; mState = READY; return NO_ERROR; @@ -781,7 +788,11 @@ void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftV // wrong audio audio buffer size (mAudioBufferSize) unsigned long toggle = mToggle ^ 1; void *userData = (void *)((unsigned long)this | toggle); - audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(numChannels); + audio_channel_mask_t sampleChannelMask = sample->channelMask(); + // When sample contains a not none channel mask, use it as is. + // Otherwise, use channel count to calculate channel mask. + audio_channel_mask_t channelMask = sampleChannelMask != AUDIO_CHANNEL_NONE + ? sampleChannelMask : audio_channel_out_mask_from_count(numChannels); // do not create a new audio track if current track is compatible with sample parameters #ifdef USE_SHARED_MEM_BUFFER diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h index 5c48a907c832..9d7410305c2c 100644 --- a/media/jni/soundpool/SoundPool.h +++ b/media/jni/soundpool/SoundPool.h @@ -58,6 +58,7 @@ public: int numChannels() { return mNumChannels; } int sampleRate() { return mSampleRate; } audio_format_t format() { return mFormat; } + audio_channel_mask_t channelMask() { return mChannelMask; } size_t size() { return mSize; } int state() { return mState; } uint8_t* data() { return static_cast<uint8_t*>(mData->pointer()); } @@ -68,18 +69,19 @@ public: private: void init(); - size_t mSize; - volatile int32_t mRefCount; - uint16_t mSampleID; - uint16_t mSampleRate; - uint8_t mState; - uint8_t mNumChannels; - audio_format_t mFormat; - int mFd; - int64_t mOffset; - int64_t mLength; - sp<IMemory> mData; - sp<MemoryHeapBase> mHeap; + size_t mSize; + volatile int32_t mRefCount; + uint16_t mSampleID; + uint16_t mSampleRate; + uint8_t mState; + uint8_t mNumChannels; + audio_format_t mFormat; + audio_channel_mask_t mChannelMask; + int mFd; + int64_t mOffset; + int64_t mLength; + sp<IMemory> mData; + sp<MemoryHeapBase> mHeap; }; // stores pending events for stolen channels diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java index 12df263ffede..1737b644f5d3 100644 --- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java +++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java @@ -26,7 +26,6 @@ import android.app.KeyguardManager; import android.car.Car; import android.car.CarNotConnectedException; import android.car.media.CarAudioManager; -import android.car.media.ICarVolumeCallback; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; @@ -106,9 +105,16 @@ public class CarVolumeDialogImpl implements VolumeDialog { private ListItemAdapter mPagedListAdapter; private Car mCar; private CarAudioManager mCarAudioManager; - private final ICarVolumeCallback mVolumeChangeCallback = new ICarVolumeCallback.Stub() { + private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback = + new CarAudioManager.CarVolumeCallback() { @Override - public void onGroupVolumeChanged(int groupId, int flags) { + public void onGroupVolumeChanged(int zoneId, int groupId, int flags) { + // TODO: Include zoneId into consideration. + // For instance + // - single display + single-zone, ignore zoneId + // - multi-display + single-zone, zoneId is fixed, may show volume bar on all displays + // - single-display + multi-zone, may show volume bar on primary display only + // - multi-display + multi-zone, may show volume bar on display specified by zoneId VolumeItem volumeItem = mAvailableVolumeItems.get(groupId); int value = getSeekbarValue(mCarAudioManager, groupId); // Do not update the progress if it is the same as before. When car audio manager sets @@ -124,7 +130,7 @@ public class CarVolumeDialogImpl implements VolumeDialog { } @Override - public void onMasterMuteChanged(int flags) { + public void onMasterMuteChanged(int zoneId, int flags) { // ignored } }; @@ -156,7 +162,7 @@ public class CarVolumeDialogImpl implements VolumeDialog { if (mPagedListAdapter != null) { mPagedListAdapter.notifyDataSetChanged(); } - mCarAudioManager.registerVolumeCallback(mVolumeChangeCallback.asBinder()); + mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback); } catch (CarNotConnectedException e) { Log.e(TAG, "Car is not connected!", e); } @@ -434,11 +440,7 @@ public class CarVolumeDialogImpl implements VolumeDialog { } private void cleanupAudioManager() { - try { - mCarAudioManager.unregisterVolumeCallback(mVolumeChangeCallback.asBinder()); - } catch (CarNotConnectedException e) { - Log.e(TAG, "Car is not connected!", e); - } + mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback); mVolumeLineItems.clear(); mCarAudioManager = null; } diff --git a/packages/ExtServices/res/values/strings.xml b/packages/ExtServices/res/values/strings.xml index 617e49ac1120..a9a5450c536e 100644 --- a/packages/ExtServices/res/values/strings.xml +++ b/packages/ExtServices/res/values/strings.xml @@ -22,5 +22,6 @@ <string name="autofill_field_classification_default_algorithm">EDIT_DISTANCE</string> <string-array name="autofill_field_classification_available_algorithms"> <item>EDIT_DISTANCE</item> + <item>EXACT_MATCH</item> </string-array> </resources> diff --git a/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java index 9ba7e092f34b..e379db842b3b 100644 --- a/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java +++ b/packages/ExtServices/src/android/ext/services/autofill/AutofillFieldClassificationServiceImpl.java @@ -15,8 +15,6 @@ */ package android.ext.services.autofill; -import static android.ext.services.autofill.EditDistanceScorer.DEFAULT_ALGORITHM; - import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; @@ -26,27 +24,72 @@ import android.view.autofill.AutofillValue; import com.android.internal.util.ArrayUtils; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class AutofillFieldClassificationServiceImpl extends AutofillFieldClassificationService { private static final String TAG = "AutofillFieldClassificationServiceImpl"; + private static final String DEFAULT_ALGORITHM = REQUIRED_ALGORITHM_EDIT_DISTANCE; + @Nullable @Override - public float[][] onGetScores(@Nullable String algorithmName, - @Nullable Bundle algorithmArgs, @NonNull List<AutofillValue> actualValues, - @NonNull List<String> userDataValues) { + /** @hide */ + public float[][] onCalculateScores(@NonNull List<AutofillValue> actualValues, + @NonNull List<String> userDataValues, @NonNull List<String> categoryIds, + @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs, + @Nullable Map algorithms, @Nullable Map args) { if (ArrayUtils.isEmpty(actualValues) || ArrayUtils.isEmpty(userDataValues)) { - Log.w(TAG, "getScores(): empty currentvalues (" + actualValues + ") or userValues (" - + userDataValues + ")"); + Log.w(TAG, "calculateScores(): empty currentvalues (" + actualValues + + ") or userValues (" + userDataValues + ")"); return null; } - if (algorithmName != null && !algorithmName.equals(DEFAULT_ALGORITHM)) { - Log.w(TAG, "Ignoring invalid algorithm (" + algorithmName + ") and using " - + DEFAULT_ALGORITHM + " instead"); - } - return EditDistanceScorer.getScores(actualValues, userDataValues); + return calculateScores(actualValues, userDataValues, categoryIds, defaultAlgorithm, + defaultArgs, (HashMap<String, String>) algorithms, + (HashMap<String, Bundle>) args); + } + + /** @hide */ + public float[][] calculateScores(@NonNull List<AutofillValue> actualValues, + @NonNull List<String> userDataValues, @NonNull List<String> categoryIds, + @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs, + @Nullable HashMap<String, String> algorithms, + @Nullable HashMap<String, Bundle> args) { + final int actualValuesSize = actualValues.size(); + final int userDataValuesSize = userDataValues.size(); + final float[][] scores = new float[actualValuesSize][userDataValuesSize]; + + for (int j = 0; j < userDataValuesSize; j++) { + final String categoryId = categoryIds.get(j); + String algorithmName = defaultAlgorithm; + Bundle arg = defaultArgs; + if (algorithms != null && algorithms.containsKey(categoryId)) { + algorithmName = algorithms.get(categoryId); + } + if (args != null && args.containsKey(categoryId)) { + arg = args.get(categoryId); + } + + if (algorithmName == null || (!algorithmName.equals(DEFAULT_ALGORITHM) + && !algorithmName.equals(REQUIRED_ALGORITHM_EXACT_MATCH))) { + Log.w(TAG, "algorithmName is " + algorithmName + ", defaulting to " + + DEFAULT_ALGORITHM); + algorithmName = DEFAULT_ALGORITHM; + } + + for (int i = 0; i < actualValuesSize; i++) { + if (algorithmName.equals(DEFAULT_ALGORITHM)) { + scores[i][j] = EditDistanceScorer.calculateScore(actualValues.get(i), + userDataValues.get(j)); + } else { + scores[i][j] = ExactMatch.calculateScore(actualValues.get(i), + userDataValues.get(j), arg); + } + } + } + return scores; } } diff --git a/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java b/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java index 302b16022c26..6a47901aa58e 100644 --- a/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java +++ b/packages/ExtServices/src/android/ext/services/autofill/EditDistanceScorer.java @@ -17,13 +17,10 @@ package android.ext.services.autofill; import android.annotation.NonNull; import android.annotation.Nullable; -import android.util.Log; import android.view.autofill.AutofillValue; import com.android.internal.annotations.VisibleForTesting; -import java.util.List; - final class EditDistanceScorer { private static final String TAG = "EditDistanceScorer"; @@ -31,15 +28,14 @@ final class EditDistanceScorer { // TODO(b/70291841): STOPSHIP - set to false before launching private static final boolean DEBUG = true; - static final String DEFAULT_ALGORITHM = "EDIT_DISTANCE"; - /** * Gets the field classification score of 2 values based on the edit distance between them. * * <p>The score is defined as: @(max_length - edit_distance) / max_length */ @VisibleForTesting - static float getScore(@Nullable AutofillValue actualValue, @Nullable String userDataValue) { + static float calculateScore(@Nullable AutofillValue actualValue, + @Nullable String userDataValue) { if (actualValue == null || !actualValue.isText() || userDataValue == null) return 0; final String actualValueText = actualValue.getTextValue().toString(); @@ -123,26 +119,5 @@ final class EditDistanceScorer { return d[m][n]; } - /** - * Gets the scores in a batch. - */ - static float[][] getScores(@NonNull List<AutofillValue> actualValues, - @NonNull List<String> userDataValues) { - final int actualValuesSize = actualValues.size(); - final int userDataValuesSize = userDataValues.size(); - if (DEBUG) { - Log.d(TAG, "getScores() will return a " + actualValuesSize + "x" - + userDataValuesSize + " matrix for " + DEFAULT_ALGORITHM); - } - final float[][] scores = new float[actualValuesSize][userDataValuesSize]; - - for (int i = 0; i < actualValuesSize; i++) { - for (int j = 0; j < userDataValuesSize; j++) { - final float score = getScore(actualValues.get(i), userDataValues.get(j)); - scores[i][j] = score; - } - } - return scores; - } } diff --git a/packages/ExtServices/src/android/ext/services/autofill/ExactMatch.java b/packages/ExtServices/src/android/ext/services/autofill/ExactMatch.java new file mode 100644 index 000000000000..3e55c5c59e02 --- /dev/null +++ b/packages/ExtServices/src/android/ext/services/autofill/ExactMatch.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 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.ext.services.autofill; + +import android.annotation.Nullable; +import android.os.Bundle; +import android.view.autofill.AutofillValue; + +import com.android.internal.annotations.VisibleForTesting; + +final class ExactMatch { + + /** + * Gets the field classification score of 2 values based on whether they are an exact match + * + * @return {@code 1.0} if the two values are an exact match, {@code 0.0} otherwise. + */ + @VisibleForTesting + static float calculateScore(@Nullable AutofillValue actualValue, + @Nullable String userDataValue, @Nullable Bundle args) { + if (actualValue == null || !actualValue.isText() || userDataValue == null) return 0; + + final String actualValueText = actualValue.getTextValue().toString(); + + final int suffixLength; + if (args != null) { + suffixLength = args.getInt("suffix", -1); + + if (suffixLength < 0) { + throw new IllegalArgumentException("suffix argument is invalid"); + } + + final String actualValueSuffix; + if (suffixLength < actualValueText.length()) { + actualValueSuffix = actualValueText.substring(actualValueText.length() + - suffixLength); + } else { + actualValueSuffix = actualValueText; + } + + final String userDataValueSuffix; + if (suffixLength < userDataValue.length()) { + userDataValueSuffix = userDataValue.substring(userDataValue.length() + - suffixLength); + } else { + userDataValueSuffix = userDataValue; + } + + return (actualValueSuffix.equalsIgnoreCase(userDataValueSuffix)) ? 1 : 0; + } else { + return actualValueText.equalsIgnoreCase(userDataValue) ? 1 : 0; + } + } +} diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java index 0cad5af00267..133d8ba8357b 100644 --- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java +++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java @@ -27,20 +27,15 @@ import android.app.ActivityThread; import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; -import android.content.ContentResolver; import android.content.Context; import android.content.pm.IPackageManager; -import android.database.ContentObserver; import android.ext.services.notification.AgingHelper.Callback; -import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; -import android.os.Handler; import android.os.SystemProperties; import android.os.UserHandle; import android.os.storage.StorageManager; -import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.NotificationAssistantService; import android.service.notification.NotificationStats; @@ -92,8 +87,6 @@ public class Assistant extends NotificationAssistantService { PREJUDICAL_DISMISSALS.add(REASON_LISTENER_CANCEL); } - private float mDismissToViewRatioLimit; - private int mStreakLimit; private SmartActionsHelper mSmartActionsHelper; private NotificationCategorizer mNotificationCategorizer; private AgingHelper mAgingHelper; @@ -107,7 +100,11 @@ public class Assistant extends NotificationAssistantService { private Ranking mFakeRanking = null; private AtomicFile mFile = null; private IPackageManager mPackageManager; - protected SettingsObserver mSettingsObserver; + + @VisibleForTesting + protected AssistantSettings.Factory mSettingsFactory = AssistantSettings.FACTORY; + @VisibleForTesting + protected AssistantSettings mSettings; public Assistant() { } @@ -118,7 +115,8 @@ public class Assistant extends NotificationAssistantService { // Contexts are correctly hooked up by the creation step, which is required for the observer // to be hooked up/initialized. mPackageManager = ActivityThread.getPackageManager(); - mSettingsObserver = new SettingsObserver(mHandler); + mSettings = mSettingsFactory.createAndRegister(mHandler, + getApplicationContext().getContentResolver(), getUserId(), this::updateThresholds); mSmartActionsHelper = new SmartActionsHelper(); mNotificationCategorizer = new NotificationCategorizer(); mAgingHelper = new AgingHelper(getContext(), @@ -216,11 +214,11 @@ public class Assistant extends NotificationAssistantService { if (!isForCurrentUser(sbn)) { return null; } - NotificationEntry entry = new NotificationEntry( - ActivityThread.getPackageManager(), sbn, channel); + NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, channel); ArrayList<Notification.Action> actions = - mSmartActionsHelper.suggestActions(this, entry); - ArrayList<CharSequence> replies = mSmartActionsHelper.suggestReplies(this, entry); + mSmartActionsHelper.suggestActions(this, entry, mSettings); + ArrayList<CharSequence> replies = + mSmartActionsHelper.suggestReplies(this, entry, mSettings); return createEnqueuedNotificationAdjustment(entry, actions, replies); } @@ -239,8 +237,7 @@ public class Assistant extends NotificationAssistantService { if (!smartReplies.isEmpty()) { signals.putCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES, smartReplies); } - if (Settings.Secure.getInt(getContentResolver(), - Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1) == 1) { + if (mSettings.mNewInterruptionModel) { if (mNotificationCategorizer.shouldSilence(entry)) { final int importance = entry.getImportance() < IMPORTANCE_LOW ? entry.getImportance() : IMPORTANCE_LOW; @@ -460,6 +457,11 @@ public class Assistant extends NotificationAssistantService { } @VisibleForTesting + public void setSmartActionsHelper(SmartActionsHelper smartActionsHelper) { + mSmartActionsHelper = smartActionsHelper; + } + + @VisibleForTesting public ChannelImpressions getImpressions(String key) { synchronized (mkeyToImpressions) { return mkeyToImpressions.get(key); @@ -475,10 +477,20 @@ public class Assistant extends NotificationAssistantService { private ChannelImpressions createChannelImpressionsWithThresholds() { ChannelImpressions impressions = new ChannelImpressions(); - impressions.updateThresholds(mDismissToViewRatioLimit, mStreakLimit); + impressions.updateThresholds(mSettings.mDismissToViewRatioLimit, mSettings.mStreakLimit); return impressions; } + private void updateThresholds() { + // Update all existing channel impression objects with any new limits/thresholds. + synchronized (mkeyToImpressions) { + for (ChannelImpressions channelImpressions: mkeyToImpressions.values()) { + channelImpressions.updateThresholds( + mSettings.mDismissToViewRatioLimit, mSettings.mStreakLimit); + } + } + } + protected final class AgingCallback implements Callback { @Override public void sendAdjustment(String key, int newImportance) { @@ -495,51 +507,4 @@ public class Assistant extends NotificationAssistantService { } } - /** - * Observer for updates on blocking helper threshold values. - */ - protected final class SettingsObserver extends ContentObserver { - private final Uri STREAK_LIMIT_URI = - Settings.Global.getUriFor(Settings.Global.BLOCKING_HELPER_STREAK_LIMIT); - private final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI = - Settings.Global.getUriFor( - Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT); - - public SettingsObserver(Handler handler) { - super(handler); - ContentResolver resolver = getApplicationContext().getContentResolver(); - resolver.registerContentObserver( - DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, getUserId()); - resolver.registerContentObserver(STREAK_LIMIT_URI, false, this, getUserId()); - - // Update all uris on creation. - update(null); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - update(uri); - } - - private void update(Uri uri) { - ContentResolver resolver = getApplicationContext().getContentResolver(); - if (uri == null || DISMISS_TO_VIEW_RATIO_LIMIT_URI.equals(uri)) { - mDismissToViewRatioLimit = Settings.Global.getFloat( - resolver, Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, - ChannelImpressions.DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT); - } - if (uri == null || STREAK_LIMIT_URI.equals(uri)) { - mStreakLimit = Settings.Global.getInt( - resolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, - ChannelImpressions.DEFAULT_STREAK_LIMIT); - } - - // Update all existing channel impression objects with any new limits/thresholds. - synchronized (mkeyToImpressions) { - for (ChannelImpressions channelImpressions: mkeyToImpressions.values()) { - channelImpressions.updateThresholds(mDismissToViewRatioLimit, mStreakLimit); - } - } - } - } } diff --git a/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java new file mode 100644 index 000000000000..39a1676b4ec7 --- /dev/null +++ b/packages/ExtServices/src/android/ext/services/notification/AssistantSettings.java @@ -0,0 +1,140 @@ +/** + * Copyright (C) 2018 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.ext.services.notification; + +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.provider.Settings; +import android.util.KeyValueListParser; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Observes the settings for {@link Assistant}. + */ +final class AssistantSettings extends ContentObserver { + public static Factory FACTORY = AssistantSettings::createAndRegister; + private static final boolean DEFAULT_GENERATE_REPLIES = true; + private static final boolean DEFAULT_GENERATE_ACTIONS = true; + private static final int DEFAULT_NEW_INTERRUPTION_MODEL_INT = 1; + + private static final Uri STREAK_LIMIT_URI = + Settings.Global.getUriFor(Settings.Global.BLOCKING_HELPER_STREAK_LIMIT); + private static final Uri DISMISS_TO_VIEW_RATIO_LIMIT_URI = + Settings.Global.getUriFor( + Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT); + private static final Uri SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI = + Settings.Global.getUriFor( + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS); + private static final Uri NOTIFICATION_NEW_INTERRUPTION_MODEL_URI = + Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL); + + private static final String KEY_GENERATE_REPLIES = "generate_replies"; + private static final String KEY_GENERATE_ACTIONS = "generate_actions"; + + private final KeyValueListParser mParser = new KeyValueListParser(','); + private final ContentResolver mResolver; + private final int mUserId; + + @VisibleForTesting + protected final Runnable mOnUpdateRunnable; + + // Actuall configuration settings. + float mDismissToViewRatioLimit; + int mStreakLimit; + boolean mGenerateReplies = DEFAULT_GENERATE_REPLIES; + boolean mGenerateActions = DEFAULT_GENERATE_ACTIONS; + boolean mNewInterruptionModel; + + private AssistantSettings(Handler handler, ContentResolver resolver, int userId, + Runnable onUpdateRunnable) { + super(handler); + mResolver = resolver; + mUserId = userId; + mOnUpdateRunnable = onUpdateRunnable; + } + + private static AssistantSettings createAndRegister( + Handler handler, ContentResolver resolver, int userId, Runnable onUpdateRunnable) { + AssistantSettings assistantSettings = + new AssistantSettings(handler, resolver, userId, onUpdateRunnable); + assistantSettings.register(); + return assistantSettings; + } + + /** + * Creates an instance but doesn't register it as an observer. + */ + @VisibleForTesting + protected static AssistantSettings createForTesting( + Handler handler, ContentResolver resolver, int userId, Runnable onUpdateRunnable) { + return new AssistantSettings(handler, resolver, userId, onUpdateRunnable); + } + + private void register() { + mResolver.registerContentObserver( + DISMISS_TO_VIEW_RATIO_LIMIT_URI, false, this, mUserId); + mResolver.registerContentObserver(STREAK_LIMIT_URI, false, this, mUserId); + mResolver.registerContentObserver( + SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI, false, this, mUserId); + + // Update all uris on creation. + update(null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + update(uri); + } + + private void update(Uri uri) { + if (uri == null || DISMISS_TO_VIEW_RATIO_LIMIT_URI.equals(uri)) { + mDismissToViewRatioLimit = Settings.Global.getFloat( + mResolver, Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, + ChannelImpressions.DEFAULT_DISMISS_TO_VIEW_RATIO_LIMIT); + } + if (uri == null || STREAK_LIMIT_URI.equals(uri)) { + mStreakLimit = Settings.Global.getInt( + mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, + ChannelImpressions.DEFAULT_STREAK_LIMIT); + } + if (uri == null || SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS_URI.equals(uri)) { + mParser.setString( + Settings.Global.getString(mResolver, + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS)); + mGenerateReplies = + mParser.getBoolean(KEY_GENERATE_REPLIES, DEFAULT_GENERATE_REPLIES); + mGenerateActions = + mParser.getBoolean(KEY_GENERATE_ACTIONS, DEFAULT_GENERATE_ACTIONS); + } + if (uri == null || NOTIFICATION_NEW_INTERRUPTION_MODEL_URI.equals(uri)) { + int mNewInterruptionModelInt = Settings.Secure.getInt( + mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, + DEFAULT_NEW_INTERRUPTION_MODEL_INT); + mNewInterruptionModel = mNewInterruptionModelInt == 1; + } + + mOnUpdateRunnable.run(); + } + + public interface Factory { + AssistantSettings createAndRegister(Handler handler, ContentResolver resolver, int userId, + Runnable onUpdateRunnable); + } +} diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java index 892267b22058..38df9b0a6fdc 100644 --- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java @@ -18,6 +18,7 @@ package android.ext.services.notification; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; +import android.app.Person; import android.app.RemoteAction; import android.content.Context; import android.os.Bundle; @@ -31,8 +32,14 @@ import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextLinks; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Deque; import java.util.List; import java.util.stream.Collectors; @@ -50,6 +57,8 @@ public class SmartActionsHelper { private static final int MAX_ACTIONS_PER_LINK = 1; private static final int MAX_SMART_ACTIONS = 3; private static final int MAX_SUGGESTED_REPLIES = 3; + // TODO: Make this configurable. + private static final int MAX_MESSAGES_TO_EXTRACT = 5; private static final ConversationActions.TypeConfig TYPE_CONFIG = new ConversationActions.TypeConfig.Builder().setIncludedTypes( @@ -64,13 +73,13 @@ public class SmartActionsHelper { /** * Adds action adjustments based on the notification contents. - * - * TODO: Once we have a API in {@link TextClassificationManager} to predict smart actions - * from notification text / message, we can replace most of the code here by consuming that API. */ @NonNull - ArrayList<Notification.Action> suggestActions( - @Nullable Context context, @NonNull NotificationEntry entry) { + ArrayList<Notification.Action> suggestActions(@Nullable Context context, + @NonNull NotificationEntry entry, @NonNull AssistantSettings settings) { + if (!settings.mGenerateActions) { + return EMPTY_ACTION_LIST; + } if (!isEligibleForActionAdjustment(entry)) { return EMPTY_ACTION_LIST; } @@ -81,13 +90,20 @@ public class SmartActionsHelper { if (tcm == null) { return EMPTY_ACTION_LIST; } + List<ConversationActions.Message> messages = extractMessages(entry.getNotification()); + if (messages.isEmpty()) { + return EMPTY_ACTION_LIST; + } + // TODO: Move to TextClassifier.suggestConversationActions once it is ready. return suggestActionsFromText( - tcm, - getMostSalientActionText(entry.getNotification()), MAX_SMART_ACTIONS); + tcm, messages.get(messages.size() - 1).getText(), MAX_SMART_ACTIONS); } - ArrayList<CharSequence> suggestReplies( - @Nullable Context context, @NonNull NotificationEntry entry) { + ArrayList<CharSequence> suggestReplies(@Nullable Context context, + @NonNull NotificationEntry entry, @NonNull AssistantSettings settings) { + if (!settings.mGenerateReplies) { + return EMPTY_REPLY_LIST; + } if (!isEligibleForReplyAdjustment(entry)) { return EMPTY_REPLY_LIST; } @@ -98,14 +114,12 @@ public class SmartActionsHelper { if (tcm == null) { return EMPTY_REPLY_LIST; } - CharSequence text = getMostSalientActionText(entry.getNotification()); - ConversationActions.Message message = - new ConversationActions.Message.Builder() - .setText(text) - .build(); - + List<ConversationActions.Message> messages = extractMessages(entry.getNotification()); + if (messages.isEmpty()) { + return EMPTY_REPLY_LIST; + } ConversationActions.Request request = - new ConversationActions.Request.Builder(Collections.singletonList(message)) + new ConversationActions.Request.Builder(messages) .setMaxSuggestions(MAX_SUGGESTED_REPLIES) .setHints(HINTS) .setTypeConfig(TYPE_CONFIG) @@ -134,10 +148,6 @@ public class SmartActionsHelper { if (!Process.myUserHandle().equals(entry.getSbn().getUser())) { return false; } - if (notification.actions != null - && notification.actions.length >= Notification.MAX_ACTION_BUTTONS) { - return false; - } if ((notification.flags & FLAG_MASK_INELGIBILE_FOR_ACTIONS) != 0) { return false; } @@ -170,21 +180,41 @@ public class SmartActionsHelper { /** Returns the text most salient for action extraction in a notification. */ @Nullable - private CharSequence getMostSalientActionText(@NonNull Notification notification) { - /* If it's messaging style, use the most recent message. */ - // TODO: Use the last few X messages instead and take the Person object into consideration. + private List<ConversationActions.Message> extractMessages(@NonNull Notification notification) { Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES); - if (messages != null && messages.length != 0) { - Bundle lastMessage = (Bundle) messages[messages.length - 1]; - CharSequence lastMessageText = - lastMessage.getCharSequence(Notification.MessagingStyle.Message.KEY_TEXT); - if (!TextUtils.isEmpty(lastMessageText)) { - return lastMessageText; + if (messages == null || messages.length == 0) { + return Arrays.asList(new ConversationActions.Message.Builder( + ConversationActions.Message.PERSON_USER_REMOTE) + .setText(notification.extras.getCharSequence(Notification.EXTRA_TEXT)) + .build()); + } + Person localUser = notification.extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON); + Deque<ConversationActions.Message> extractMessages = new ArrayDeque<>(); + for (int i = messages.length - 1; i >= 0; i--) { + Notification.MessagingStyle.Message message = + Notification.MessagingStyle.Message.getMessageFromBundle((Bundle) messages[i]); + if (message == null) { + continue; + } + Person senderPerson = message.getSenderPerson(); + // Skip encoding once the sender is missing as it is important to distinguish + // local user and remote user when generating replies. + if (senderPerson == null) { + break; + } + Person author = localUser != null && localUser.equals(senderPerson) + ? ConversationActions.Message.PERSON_USER_LOCAL : senderPerson; + extractMessages.push(new ConversationActions.Message.Builder(author) + .setText(message.getText()) + .setReferenceTime( + ZonedDateTime.ofInstant(Instant.ofEpochMilli(message.getTimestamp()), + ZoneOffset.systemDefault())) + .build()); + if (extractMessages.size() >= MAX_MESSAGES_TO_EXTRACT) { + break; } } - - // Fall back to using the normal text. - return notification.extras.getCharSequence(Notification.EXTRA_TEXT); + return new ArrayList<>(extractMessages); } /** Returns a list of actions to act on entities in a given piece of text. */ diff --git a/packages/ExtServices/tests/Android.mk b/packages/ExtServices/tests/Android.mk index 0a95b858a93e..a57fa9458f08 100644 --- a/packages/ExtServices/tests/Android.mk +++ b/packages/ExtServices/tests/Android.mk @@ -12,7 +12,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ mockito-target-minus-junit4 \ espresso-core \ truth-prebuilt \ - testables + testables \ + testng # Include all test java files. LOCAL_SRC_FILES := $(call all-java-files-under, src) diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java index 48c076e67e78..6fda4c73792b 100644 --- a/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/autofill/AutofillFieldClassificationServiceImplTest.java @@ -16,15 +16,22 @@ package android.ext.services.autofill; -import org.junit.Test; - -import java.util.Arrays; -import java.util.Collections; +import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EXACT_MATCH; +import static android.view.autofill.AutofillValue.forText; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import android.os.Bundle; import android.view.autofill.AutofillValue; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + /** * Contains the base tests that does not rely on the specific algorithm implementation. */ @@ -34,26 +41,78 @@ public class AutofillFieldClassificationServiceImplTest { new AutofillFieldClassificationServiceImpl(); @Test - public void testOnGetScores_nullActualValues() { - assertThat(mService.onGetScores(null, null, null, Arrays.asList("whatever"))).isNull(); + public void testOnCalculateScores_nullActualValues() { + assertThat(mService.onCalculateScores(null, null, null, null, null, null, null)).isNull(); } @Test - public void testOnGetScores_emptyActualValues() { - assertThat(mService.onGetScores(null, null, Collections.emptyList(), - Arrays.asList("whatever"))).isNull(); + public void testOnCalculateScores_emptyActualValues() { + assertThat(mService.onCalculateScores(Collections.emptyList(), Arrays.asList("whatever"), + null, null, null, null, null)).isNull(); } @Test - public void testOnGetScores_nullUserDataValues() { - assertThat(mService.onGetScores(null, null, - Arrays.asList(AutofillValue.forText("whatever")), null)).isNull(); + public void testOnCalculateScores_nullUserDataValues() { + assertThat(mService.onCalculateScores(Arrays.asList(AutofillValue.forText("whatever")), + null, null, null, null, null, null)).isNull(); } @Test - public void testOnGetScores_emptyUserDataValues() { - assertThat(mService.onGetScores(null, null, - Arrays.asList(AutofillValue.forText("whatever")), Collections.emptyList())) - .isNull(); + public void testOnCalculateScores_emptyUserDataValues() { + assertThat(mService.onCalculateScores(Arrays.asList(AutofillValue.forText("whatever")), + Collections.emptyList(), null, null, null, null, null)) + .isNull(); + } + + @Test + public void testCalculateScores() { + final List<AutofillValue> actualValues = Arrays.asList(forText("A"), forText("b"), + forText("dude")); + final List<String> userDataValues = Arrays.asList("a", "b", "B", "ab", "c", "dude", + "sweet_dude", "dude_sweet"); + final List<String> categoryIds = Arrays.asList("cat", "cat", "cat", "cat", "cat", "last4", + "last4", "last4"); + final HashMap<String, String> algorithms = new HashMap<>(1); + algorithms.put("last4", REQUIRED_ALGORITHM_EXACT_MATCH); + + final Bundle last4Bundle = new Bundle(); + last4Bundle.putInt("suffix", 4); + + final HashMap<String, Bundle> args = new HashMap<>(1); + args.put("last4", last4Bundle); + + final float[][] expectedScores = new float[][] { + new float[] { 1F, 0F, 0F, 0.5F, 0F, 0F, 0F, 0F }, + new float[] { 0F, 1F, 1F, 0.5F, 0F, 0F, 0F, 0F }, + new float[] { 0F, 0F, 0F, 0F , 0F, 1F, 1F, 0F } + }; + final float[][] actualScores = mService.onCalculateScores(actualValues, userDataValues, + categoryIds, null, null, algorithms, args); + + // Unfortunately, Truth does not have an easy way to compare float matrices and show useful + // messages in case of error, so we need to check. + assertWithMessage("actual=%s, expected=%s", toString(actualScores), + toString(expectedScores)).that(actualScores.length).isEqualTo(3); + for (int i = 0; i < 3; i++) { + assertWithMessage("actual=%s, expected=%s", toString(actualScores), + toString(expectedScores)).that(actualScores[i].length).isEqualTo(8); + } + + for (int i = 0; i < actualScores.length; i++) { + final float[] line = actualScores[i]; + for (int j = 0; j < line.length; j++) { + float cell = line[j]; + assertWithMessage("wrong score at [%s, %s]", i, j).that(cell).isWithin(0.01F) + .of(expectedScores[i][j]); + } + } + } + + public static String toString(float[][] matrix) { + final StringBuilder string = new StringBuilder("[ "); + for (int i = 0; i < matrix.length; i++) { + string.append(Arrays.toString(matrix[i])).append(" "); + } + return string.append(" ]").toString(); } } diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java index afe223641d37..9b9d4be59929 100644 --- a/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/autofill/EditDistanceScorerTest.java @@ -15,107 +15,67 @@ */ package android.ext.services.autofill; -import static android.ext.services.autofill.EditDistanceScorer.getScore; -import static android.ext.services.autofill.EditDistanceScorer.getScores; -import static android.view.autofill.AutofillValue.forText; +import static android.ext.services.autofill.EditDistanceScorer.calculateScore; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import android.view.autofill.AutofillValue; import org.junit.Test; -import java.util.Arrays; -import java.util.List; - public class EditDistanceScorerTest { @Test - public void testGetScore_nullValue() { - assertFloat(getScore(null, "D'OH!"), 0); + public void testCalculateScore_nullValue() { + assertFloat(calculateScore(null, "D'OH!"), 0); } @Test - public void testGetScore_nonTextValue() { - assertFloat(getScore(AutofillValue.forToggle(true), "D'OH!"), 0); + public void testCalculateScore_nonTextValue() { + assertFloat(calculateScore(AutofillValue.forToggle(true), "D'OH!"), 0); } @Test - public void testGetScore_nullUserData() { - assertFloat(getScore(AutofillValue.forText("D'OH!"), null), 0); + public void testCalculateScore_nullUserData() { + assertFloat(calculateScore(AutofillValue.forText("D'OH!"), null), 0); } @Test - public void testGetScore_fullMatch() { - assertFloat(getScore(AutofillValue.forText("D'OH!"), "D'OH!"), 1); - assertFloat(getScore(AutofillValue.forText(""), ""), 1); + public void testCalculateScore_fullMatch() { + assertFloat(calculateScore(AutofillValue.forText("D'OH!"), "D'OH!"), 1); + assertFloat(calculateScore(AutofillValue.forText(""), ""), 1); } @Test - public void testGetScore_fullMatchMixedCase() { - assertFloat(getScore(AutofillValue.forText("D'OH!"), "D'oH!"), 1); + public void testCalculateScore_fullMatchMixedCase() { + assertFloat(calculateScore(AutofillValue.forText("D'OH!"), "D'oH!"), 1); } @Test - public void testGetScore_mismatchDifferentSizes() { - assertFloat(getScore(AutofillValue.forText("X"), "Xy"), 0.50F); - assertFloat(getScore(AutofillValue.forText("Xy"), "X"), 0.50F); - assertFloat(getScore(AutofillValue.forText("One"), "MoreThanOne"), 0.27F); - assertFloat(getScore(AutofillValue.forText("MoreThanOne"), "One"), 0.27F); - assertFloat(getScore(AutofillValue.forText("1600 Amphitheatre Parkway"), + public void testCalculateScore_mismatchDifferentSizes() { + assertFloat(calculateScore(AutofillValue.forText("X"), "Xy"), 0.50F); + assertFloat(calculateScore(AutofillValue.forText("Xy"), "X"), 0.50F); + assertFloat(calculateScore(AutofillValue.forText("One"), "MoreThanOne"), 0.27F); + assertFloat(calculateScore(AutofillValue.forText("MoreThanOne"), "One"), 0.27F); + assertFloat(calculateScore(AutofillValue.forText("1600 Amphitheatre Parkway"), "1600 Amphitheatre Pkwy"), 0.88F); - assertFloat(getScore(AutofillValue.forText("1600 Amphitheatre Pkwy"), + assertFloat(calculateScore(AutofillValue.forText("1600 Amphitheatre Pkwy"), "1600 Amphitheatre Parkway"), 0.88F); } @Test - public void testGetScore_partialMatch() { - assertFloat(getScore(AutofillValue.forText("Dude"), "Dxxx"), 0.25F); - assertFloat(getScore(AutofillValue.forText("Dude"), "DUxx"), 0.50F); - assertFloat(getScore(AutofillValue.forText("Dude"), "DUDx"), 0.75F); - assertFloat(getScore(AutofillValue.forText("Dxxx"), "Dude"), 0.25F); - assertFloat(getScore(AutofillValue.forText("DUxx"), "Dude"), 0.50F); - assertFloat(getScore(AutofillValue.forText("DUDx"), "Dude"), 0.75F); - } - - @Test - public void testGetScores() { - final List<AutofillValue> actualValues = Arrays.asList(forText("A"), forText("b")); - final List<String> userDataValues = Arrays.asList("a", "B", "ab", "c"); - final float[][] expectedScores = new float[][] { - new float[] { 1F, 0F, 0.5F, 0F }, - new float[] { 0F, 1F, 0.5F, 0F } - }; - final float[][] actualScores = getScores(actualValues, userDataValues); - - // Unfortunately, Truth does not have an easy way to compare float matrices and show useful - // messages in case of error, so we need to check. - assertWithMessage("actual=%s, expected=%s", toString(actualScores), - toString(expectedScores)).that(actualScores.length).isEqualTo(2); - assertWithMessage("actual=%s, expected=%s", toString(actualScores), - toString(expectedScores)).that(actualScores[0].length).isEqualTo(4); - assertWithMessage("actual=%s, expected=%s", toString(actualScores), - toString(expectedScores)).that(actualScores[1].length).isEqualTo(4); - for (int i = 0; i < actualScores.length; i++) { - final float[] line = actualScores[i]; - for (int j = 0; j < line.length; j++) { - float cell = line[j]; - assertWithMessage("wrong score at [%s, %s]", i, j).that(cell).isWithin(0.01F) - .of(expectedScores[i][j]); - } - } + public void testCalculateScore_partialMatch() { + assertFloat(calculateScore(AutofillValue.forText("Dude"), "Dxxx"), 0.25F); + assertFloat(calculateScore(AutofillValue.forText("Dude"), "DUxx"), 0.50F); + assertFloat(calculateScore(AutofillValue.forText("Dude"), "DUDx"), 0.75F); + assertFloat(calculateScore(AutofillValue.forText("Dxxx"), "Dude"), 0.25F); + assertFloat(calculateScore(AutofillValue.forText("DUxx"), "Dude"), 0.50F); + assertFloat(calculateScore(AutofillValue.forText("DUDx"), "Dude"), 0.75F); } public static void assertFloat(float actualValue, float expectedValue) { assertThat(actualValue).isWithin(0.01F).of(expectedValue); } - public static String toString(float[][] matrix) { - final StringBuilder string = new StringBuilder("[ "); - for (int i = 0; i < matrix.length; i++) { - string.append(Arrays.toString(matrix[i])).append(" "); - } - return string.append(" ]").toString(); - } + } diff --git a/packages/ExtServices/tests/src/android/ext/services/autofill/ExactMatchTest.java b/packages/ExtServices/tests/src/android/ext/services/autofill/ExactMatchTest.java new file mode 100644 index 000000000000..bf5e1609fa8b --- /dev/null +++ b/packages/ExtServices/tests/src/android/ext/services/autofill/ExactMatchTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2018 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.ext.services.autofill; + +import static android.ext.services.autofill.ExactMatch.calculateScore; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.os.Bundle; +import android.view.autofill.AutofillValue; + +import org.junit.Test; + +public class ExactMatchTest { + + private Bundle last4Bundle() { + final Bundle bundle = new Bundle(); + bundle.putInt("suffix", 4); + return bundle; + } + + @Test + public void testCalculateScore_nullValue() { + assertFloat(calculateScore(null, "TEST", null), 0); + } + + @Test + public void testCalculateScore_nonTextValue() { + assertFloat(calculateScore(AutofillValue.forToggle(true), "TEST", null), 0); + } + + @Test + public void testCalculateScore_nullUserData() { + assertFloat(calculateScore(AutofillValue.forText("TEST"), null, null), 0); + } + + @Test + public void testCalculateScore_succeedMatchMixedCases_last4() { + final Bundle last4 = last4Bundle(); + assertFloat(calculateScore(AutofillValue.forText("TEST"), "1234 test", last4), 1); + assertFloat(calculateScore(AutofillValue.forText("test"), "1234 TEST", last4), 1); + } + + @Test + public void testCalculateScore_mismatchDifferentSizes_last4() { + final Bundle last4 = last4Bundle(); + assertFloat(calculateScore(AutofillValue.forText("TEST"), "TEST1", last4), 0); + assertFloat(calculateScore(AutofillValue.forText(""), "TEST", last4), 0); + assertFloat(calculateScore(AutofillValue.forText("TEST"), "", last4), 0); + } + + @Test + public void testCalculateScore_match() { + final Bundle last4 = last4Bundle(); + assertFloat(calculateScore(AutofillValue.forText("1234 1234 1234 1234"), + "xxxx xxxx xxxx 1234", last4), 1); + assertFloat(calculateScore(AutofillValue.forText("TEST"), "TEST", null), 1); + assertFloat(calculateScore(AutofillValue.forText("TEST 1234"), "1234", last4), 1); + assertFloat(calculateScore(AutofillValue.forText("TEST"), "test", null), 1); + } + + @Test + public void testCalculateScore_badBundle() { + final Bundle bundle = new Bundle(); + bundle.putInt("suffix", -2); + assertThrows(IllegalArgumentException.class, () -> calculateScore( + AutofillValue.forText("TEST"), "TEST", bundle)); + + final Bundle largeBundle = new Bundle(); + largeBundle.putInt("suffix", 10); + assertFloat(calculateScore(AutofillValue.forText("TEST"), "TEST", largeBundle), 1); + + final Bundle stringBundle = new Bundle(); + stringBundle.putString("suffix", "value"); + assertThrows(IllegalArgumentException.class, () -> calculateScore( + AutofillValue.forText("TEST"), "TEST", stringBundle)); + + } + + public static void assertFloat(float actualValue, float expectedValue) { + assertThat(actualValue).isWithin(0.01F).of(expectedValue); + } +} diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java new file mode 100644 index 000000000000..fd23f2b78b42 --- /dev/null +++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantSettingsTest.java @@ -0,0 +1,162 @@ +/** + * Copyright (C) 2018 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.ext.services.notification; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.ContentResolver; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.testing.TestableContext; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class AssistantSettingsTest { + private static final int USER_ID = 5; + + @Rule + public final TestableContext mContext = + new TestableContext(InstrumentationRegistry.getContext(), null); + + @Mock Runnable mOnUpdateRunnable; + + private ContentResolver mResolver; + private AssistantSettings mAssistantSettings; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mResolver = mContext.getContentResolver(); + Handler handler = new Handler(Looper.getMainLooper()); + + // To bypass real calls to global settings values, set the Settings values here. + Settings.Global.putFloat(mResolver, + Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f); + Settings.Global.putInt(mResolver, Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2); + Settings.Global.putString(mResolver, + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, + "generate_replies=true,generate_actions=true"); + Settings.Secure.putInt(mResolver, Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1); + + mAssistantSettings = AssistantSettings.createForTesting( + handler, mResolver, USER_ID, mOnUpdateRunnable); + } + + @Test + public void testGenerateRepliesDisabled() { + Settings.Global.putString(mResolver, + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, + "generate_replies=false"); + + // Notify for the settings values we updated. + mAssistantSettings.onChange(false, + Settings.Global.getUriFor( + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS)); + + + assertFalse(mAssistantSettings.mGenerateReplies); + } + + @Test + public void testGenerateRepliesEnabled() { + Settings.Global.putString(mResolver, + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_replies=true"); + + // Notify for the settings values we updated. + mAssistantSettings.onChange(false, + Settings.Global.getUriFor( + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS)); + + assertTrue(mAssistantSettings.mGenerateReplies); + } + + @Test + public void testGenerateActionsDisabled() { + Settings.Global.putString(mResolver, + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=false"); + + // Notify for the settings values we updated. + mAssistantSettings.onChange(false, + Settings.Global.getUriFor( + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS)); + + assertTrue(mAssistantSettings.mGenerateReplies); + } + + @Test + public void testGenerateActionsEnabled() { + Settings.Global.putString(mResolver, + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, "generate_actions=true"); + + // Notify for the settings values we updated. + mAssistantSettings.onChange(false, + Settings.Global.getUriFor( + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS)); + + assertTrue(mAssistantSettings.mGenerateReplies); + } + + @Test + public void testStreakLimit() { + verify(mOnUpdateRunnable, never()).run(); + + // Update settings value. + int newStreakLimit = 4; + Settings.Global.putInt(mResolver, + Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, newStreakLimit); + + // Notify for the settings value we updated. + mAssistantSettings.onChange(false, Settings.Global.getUriFor( + Settings.Global.BLOCKING_HELPER_STREAK_LIMIT)); + + assertEquals(newStreakLimit, mAssistantSettings.mStreakLimit); + verify(mOnUpdateRunnable).run(); + } + + @Test + public void testDismissToViewRatioLimit() { + verify(mOnUpdateRunnable, never()).run(); + + // Update settings value. + float newDismissToViewRatioLimit = 3f; + Settings.Global.putFloat(mResolver, + Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, + newDismissToViewRatioLimit); + + // Notify for the settings value we updated. + mAssistantSettings.onChange(false, Settings.Global.getUriFor( + Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT)); + + assertEquals(newDismissToViewRatioLimit, mAssistantSettings.mDismissToViewRatioLimit, 1e-6); + verify(mOnUpdateRunnable).run(); + } +} diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java index 2eb005a9b1fa..0a95b83bdbe3 100644 --- a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java +++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java @@ -33,13 +33,11 @@ import android.app.Application; import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; -import android.content.ContentResolver; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.os.Build; import android.os.UserHandle; -import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; @@ -86,8 +84,7 @@ public class AssistantTest extends ServiceTestCase<Assistant> { @Mock INotificationManager mNoMan; @Mock AtomicFile mFile; - @Mock - IPackageManager mPackageManager; + @Mock IPackageManager mPackageManager; Assistant mAssistant; Application mApplication; @@ -108,20 +105,26 @@ public class AssistantTest extends ServiceTestCase<Assistant> { new Intent("android.service.notification.NotificationAssistantService"); startIntent.setPackage("android.ext.services"); - // To bypass real calls to global settings values, set the Settings values here. - Settings.Global.putFloat(mContext.getContentResolver(), - Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, 0.8f); - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, 2); mApplication = (Application) InstrumentationRegistry.getInstrumentation(). getTargetContext().getApplicationContext(); // Force the test to use the correct application instead of trying to use a mock application setApplication(mApplication); - bindService(startIntent); + + setupService(); mAssistant = getService(); + + // Override the AssistantSettings factory. + mAssistant.mSettingsFactory = AssistantSettings::createForTesting; + + bindService(startIntent); + + mAssistant.mSettings.mDismissToViewRatioLimit = 0.8f; + mAssistant.mSettings.mStreakLimit = 2; + mAssistant.mSettings.mNewInterruptionModel = true; mAssistant.setNoMan(mNoMan); mAssistant.setFile(mFile); mAssistant.setPackageManager(mPackageManager); + ApplicationInfo info = mock(ApplicationInfo.class); when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())) .thenReturn(info); @@ -408,6 +411,8 @@ public class AssistantTest extends ServiceTestCase<Assistant> { mAssistant.writeXml(serializer); Assistant assistant = new Assistant(); + // onCreate is not invoked, so settings won't be initialised, unless we do it here. + assistant.mSettings = mAssistant.mSettings; assistant.readXml(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray()))); assertEquals(ci1, assistant.getImpressions(key1)); @@ -417,8 +422,6 @@ public class AssistantTest extends ServiceTestCase<Assistant> { @Test public void testSettingsProviderUpdate() { - ContentResolver resolver = mApplication.getContentResolver(); - // Set up channels String key = mAssistant.getKey("pkg1", 1, "channel1"); ChannelImpressions ci = new ChannelImpressions(); @@ -435,19 +438,11 @@ public class AssistantTest extends ServiceTestCase<Assistant> { assertEquals(false, ci.shouldTriggerBlock()); // Update settings values. - float newDismissToViewRatioLimit = 0f; - int newStreakLimit = 0; - Settings.Global.putFloat(resolver, - Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT, - newDismissToViewRatioLimit); - Settings.Global.putInt(resolver, - Settings.Global.BLOCKING_HELPER_STREAK_LIMIT, newStreakLimit); + mAssistant.mSettings.mDismissToViewRatioLimit = 0f; + mAssistant.mSettings.mStreakLimit = 0; // Notify for the settings values we updated. - mAssistant.mSettingsObserver.onChange(false, Settings.Global.getUriFor( - Settings.Global.BLOCKING_HELPER_STREAK_LIMIT)); - mAssistant.mSettingsObserver.onChange(false, Settings.Global.getUriFor( - Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT)); + mAssistant.mSettings.mOnUpdateRunnable.run(); // With the new threshold, the blocking helper should be triggered. assertEquals(true, ci.shouldTriggerBlock()); diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java new file mode 100644 index 000000000000..60d31fca8ddb --- /dev/null +++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java @@ -0,0 +1,236 @@ +/** + * Copyright (C) 2018 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.ext.services.notification; + +import static com.google.common.truth.Truth.assertAbout; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.app.Notification; +import android.app.Person; +import android.content.Context; +import android.os.Process; +import android.service.notification.StatusBarNotification; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.view.textclassifier.ConversationActions; +import android.view.textclassifier.TextClassificationManager; +import android.view.textclassifier.TextClassifier; + +import com.google.common.truth.FailureStrategy; +import com.google.common.truth.Subject; +import com.google.common.truth.SubjectFactory; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import javax.annotation.Nullable; + +@RunWith(AndroidJUnit4.class) +public class SmartActionHelperTest { + + private SmartActionsHelper mSmartActionsHelper = new SmartActionsHelper(); + private Context mContext; + @Mock private TextClassifier mTextClassifier; + @Mock private NotificationEntry mNotificationEntry; + @Mock private StatusBarNotification mStatusBarNotification; + private Notification.Builder mNotificationBuilder; + private AssistantSettings mSettings; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = InstrumentationRegistry.getTargetContext(); + + mContext.getSystemService(TextClassificationManager.class) + .setTextClassifier(mTextClassifier); + when(mTextClassifier.suggestConversationActions(any(ConversationActions.Request.class))) + .thenReturn(new ConversationActions(Collections.emptyList())); + + when(mNotificationEntry.getSbn()).thenReturn(mStatusBarNotification); + // The notification is eligible to have smart suggestions. + when(mNotificationEntry.hasInlineReply()).thenReturn(true); + when(mNotificationEntry.isMessaging()).thenReturn(true); + when(mStatusBarNotification.getPackageName()).thenReturn("random.app"); + when(mStatusBarNotification.getUser()).thenReturn(Process.myUserHandle()); + mNotificationBuilder = new Notification.Builder(mContext, "channel"); + mSettings = AssistantSettings.createForTesting( + null, null, Process.myUserHandle().getIdentifier(), null); + mSettings.mGenerateActions = true; + mSettings.mGenerateReplies = true; + } + + @Test + public void testSuggestReplies_notMessagingApp() { + when(mNotificationEntry.isMessaging()).thenReturn(false); + ArrayList<CharSequence> textReplies = + mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings); + assertThat(textReplies).isEmpty(); + } + + @Test + public void testSuggestReplies_noInlineReply() { + when(mNotificationEntry.hasInlineReply()).thenReturn(false); + ArrayList<CharSequence> textReplies = + mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings); + assertThat(textReplies).isEmpty(); + } + + @Test + public void testSuggestReplies_nonMessageStyle() { + Notification notification = mNotificationBuilder.setContentText("Where are you?").build(); + when(mNotificationEntry.getNotification()).thenReturn(notification); + + List<ConversationActions.Message> messages = getMessagesInRequest(); + assertThat(messages).hasSize(1); + MessageSubject.assertThat(messages.get(0)).hasText("Where are you?"); + } + + @Test + public void testSuggestReplies_messageStyle() { + Person me = new Person.Builder().setName("Me").build(); + Person userA = new Person.Builder().setName("A").build(); + Person userB = new Person.Builder().setName("B").build(); + Notification.MessagingStyle style = + new Notification.MessagingStyle(me) + .addMessage("firstMessage", 1000, (Person) null) + .addMessage("secondMessage", 2000, me) + .addMessage("thirdMessage", 3000, userA) + .addMessage("fourthMessage", 4000, userB); + Notification notification = + mNotificationBuilder + .setContentText("You have three new messages") + .setStyle(style) + .build(); + when(mNotificationEntry.getNotification()).thenReturn(notification); + + List<ConversationActions.Message> messages = getMessagesInRequest(); + assertThat(messages).hasSize(3); + + ConversationActions.Message secondMessage = messages.get(0); + MessageSubject.assertThat(secondMessage).hasText("secondMessage"); + MessageSubject.assertThat(secondMessage) + .hasPerson(ConversationActions.Message.PERSON_USER_LOCAL); + MessageSubject.assertThat(secondMessage) + .hasReferenceTime(createZonedDateTimeFromMsUtc(2000)); + + ConversationActions.Message thirdMessage = messages.get(1); + MessageSubject.assertThat(thirdMessage).hasText("thirdMessage"); + MessageSubject.assertThat(thirdMessage).hasPerson(userA); + MessageSubject.assertThat(thirdMessage) + .hasReferenceTime(createZonedDateTimeFromMsUtc(3000)); + + ConversationActions.Message fourthMessage = messages.get(2); + MessageSubject.assertThat(fourthMessage).hasText("fourthMessage"); + MessageSubject.assertThat(fourthMessage).hasPerson(userB); + MessageSubject.assertThat(fourthMessage) + .hasReferenceTime(createZonedDateTimeFromMsUtc(4000)); + } + + @Test + public void testSuggestReplies_messageStyle_noPerson() { + Person me = new Person.Builder().setName("Me").build(); + Notification.MessagingStyle style = + new Notification.MessagingStyle(me).addMessage("message", 1000, (Person) null); + Notification notification = + mNotificationBuilder + .setContentText("You have one new message") + .setStyle(style) + .build(); + when(mNotificationEntry.getNotification()).thenReturn(notification); + + mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings); + + verify(mTextClassifier, never()) + .suggestConversationActions(any(ConversationActions.Request.class)); + } + + private ZonedDateTime createZonedDateTimeFromMsUtc(long msUtc) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(msUtc), ZoneOffset.systemDefault()); + } + + private List<ConversationActions.Message> getMessagesInRequest() { + mSmartActionsHelper.suggestReplies(mContext, mNotificationEntry, mSettings); + + ArgumentCaptor<ConversationActions.Request> argumentCaptor = + ArgumentCaptor.forClass(ConversationActions.Request.class); + verify(mTextClassifier).suggestConversationActions(argumentCaptor.capture()); + ConversationActions.Request request = argumentCaptor.getValue(); + return request.getConversation(); + } + + private static final class MessageSubject + extends Subject<MessageSubject, ConversationActions.Message> { + + private static final SubjectFactory<MessageSubject, ConversationActions.Message> FACTORY = + new SubjectFactory<MessageSubject, ConversationActions.Message>() { + @Override + public MessageSubject getSubject( + @NonNull FailureStrategy failureStrategy, + @NonNull ConversationActions.Message subject) { + return new MessageSubject(failureStrategy, subject); + } + }; + + private MessageSubject( + FailureStrategy failureStrategy, @Nullable ConversationActions.Message subject) { + super(failureStrategy, subject); + } + + private void hasText(String text) { + if (!Objects.equals(text, getSubject().getText().toString())) { + failWithBadResults("has text", text, "has", getSubject().getText()); + } + } + + private void hasPerson(Person person) { + if (!Objects.equals(person, getSubject().getAuthor())) { + failWithBadResults("has author", person, "has", getSubject().getAuthor()); + } + } + + private void hasReferenceTime(ZonedDateTime referenceTime) { + if (!Objects.equals(referenceTime, getSubject().getReferenceTime())) { + failWithBadResults( + "has reference time", + referenceTime, + "has", + getSubject().getReferenceTime()); + } + } + + private static MessageSubject assertThat(ConversationActions.Message message) { + return assertAbout(FACTORY).that(message); + } + } +} diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java index 87d6e4a137ac..be817d60e55b 100644 --- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java +++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java @@ -23,7 +23,6 @@ import android.content.IntentFilter; import android.location.Criteria; import android.os.Handler; import android.os.Looper; -import android.os.Message; import android.os.UserHandle; import android.os.WorkSource; @@ -34,87 +33,53 @@ import com.android.location.provider.ProviderRequestUnbundled; import java.io.FileDescriptor; import java.io.PrintWriter; -public class FusedLocationProvider extends LocationProviderBase implements FusionEngine.Callback { +class FusedLocationProvider extends LocationProviderBase implements FusionEngine.Callback { private static final String TAG = "FusedLocationProvider"; private static ProviderPropertiesUnbundled PROPERTIES = ProviderPropertiesUnbundled.create( false, false, false, false, true, true, true, Criteria.POWER_LOW, Criteria.ACCURACY_FINE); - private static final int MSG_ENABLE = 1; - private static final int MSG_DISABLE = 2; - private static final int MSG_SET_REQUEST = 3; - + private final Context mContext; + private final Handler mHandler; private final FusionEngine mEngine; - private static class RequestWrapper { - public ProviderRequestUnbundled request; - public WorkSource source; - public RequestWrapper(ProviderRequestUnbundled request, WorkSource source) { - this.request = request; - this.source = source; + private final BroadcastReceiver mUserSwitchReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + mEngine.switchUser(); + } } - } + }; - public FusedLocationProvider(Context context) { + FusedLocationProvider(Context context) { super(TAG, PROPERTIES); - mEngine = new FusionEngine(context, Looper.myLooper()); - // listen for user change - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_USER_SWITCHED); - context.registerReceiverAsUser(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - mEngine.switchUser(); - } - } - }, UserHandle.ALL, intentFilter, null, mHandler); + mContext = context; + mHandler = new Handler(Looper.myLooper()); + mEngine = new FusionEngine(context, Looper.myLooper(), this); } - /** - * For serializing requests to mEngine. - */ - private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_ENABLE: - mEngine.init(FusedLocationProvider.this); - break; - case MSG_DISABLE: - mEngine.deinit(); - break; - case MSG_SET_REQUEST: - { - RequestWrapper wrapper = (RequestWrapper) msg.obj; - mEngine.setRequest(wrapper.request, wrapper.source); - break; - } - } - } - }; - - @Override - public void onEnable() { - mHandler.sendEmptyMessage(MSG_ENABLE); + void init() { + // listen for user change + mContext.registerReceiverAsUser(mUserSwitchReceiver, UserHandle.ALL, + new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler); } - @Override - public void onDisable() { - mHandler.sendEmptyMessage(MSG_DISABLE); + void destroy() { + mContext.unregisterReceiver(mUserSwitchReceiver); + mHandler.post(() -> mEngine.setRequest(null)); } @Override public void onSetRequest(ProviderRequestUnbundled request, WorkSource source) { - mHandler.obtainMessage(MSG_SET_REQUEST, new RequestWrapper(request, source)).sendToTarget(); + mHandler.post(() -> mEngine.setRequest(request)); } @Override public void onDump(FileDescriptor fd, PrintWriter pw, String[] args) { - // perform synchronously mEngine.dump(fd, pw, args); } } diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java index 12966cfab888..75bb5eceab6d 100644 --- a/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java +++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java @@ -21,27 +21,24 @@ import android.content.Intent; import android.os.IBinder; public class FusedLocationService extends Service { + private FusedLocationProvider mProvider; @Override public IBinder onBind(Intent intent) { if (mProvider == null) { - mProvider = new FusedLocationProvider(getApplicationContext()); + mProvider = new FusedLocationProvider(this); + mProvider.init(); } + return mProvider.getBinder(); } @Override - public boolean onUnbind(Intent intent) { - // make sure to stop performing work + public void onDestroy() { if (mProvider != null) { - mProvider.onDisable(); + mProvider.destroy(); + mProvider = null; } - return false; - } - - @Override - public void onDestroy() { - mProvider = null; } } diff --git a/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java b/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java index 7a4952484e46..e4610cf44636 100644 --- a/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java +++ b/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java @@ -16,14 +16,6 @@ package com.android.location.fused; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.HashMap; - -import com.android.location.provider.LocationProviderBase; -import com.android.location.provider.LocationRequestUnbundled; -import com.android.location.provider.ProviderRequestUnbundled; - import android.content.Context; import android.location.Location; import android.location.LocationListener; @@ -32,9 +24,16 @@ import android.os.Bundle; import android.os.Looper; import android.os.Parcelable; import android.os.SystemClock; -import android.os.WorkSource; import android.util.Log; +import com.android.location.provider.LocationProviderBase; +import com.android.location.provider.LocationRequestUnbundled; +import com.android.location.provider.ProviderRequestUnbundled; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashMap; + public class FusionEngine implements LocationListener { public interface Callback { void reportLocation(Location location); @@ -47,72 +46,35 @@ public class FusionEngine implements LocationListener { public static final long SWITCH_ON_FRESHNESS_CLIFF_NS = 11 * 1000000000L; // 11 seconds - private final Context mContext; private final LocationManager mLocationManager; private final Looper mLooper; + private final Callback mCallback; // all fields are only used on mLooper thread. except for in dump() which is not thread-safe - private Callback mCallback; private Location mFusedLocation; private Location mGpsLocation; private Location mNetworkLocation; - private boolean mEnabled; private ProviderRequestUnbundled mRequest; private final HashMap<String, ProviderStats> mStats = new HashMap<>(); - public FusionEngine(Context context, Looper looper) { - mContext = context; + FusionEngine(Context context, Looper looper, Callback callback) { mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); mNetworkLocation = new Location(""); mNetworkLocation.setAccuracy(Float.MAX_VALUE); mGpsLocation = new Location(""); mGpsLocation.setAccuracy(Float.MAX_VALUE); mLooper = looper; + mCallback = callback; mStats.put(GPS, new ProviderStats()); mStats.put(NETWORK, new ProviderStats()); - - } - - public void init(Callback callback) { - Log.i(TAG, "engine started (" + mContext.getPackageName() + ")"); - mCallback = callback; - } - - /** - * Called to stop doing any work, and release all resources - * This can happen when a better fusion engine is installed - * in a different package, and this one is no longer needed. - * Called on mLooper thread - */ - public void deinit() { - mRequest = null; - disable(); - Log.i(TAG, "engine stopped (" + mContext.getPackageName() + ")"); - } - - /** Called on mLooper thread */ - public void enable() { - if (!mEnabled) { - mEnabled = true; - updateRequirements(); - } - } - - /** Called on mLooper thread */ - public void disable() { - if (mEnabled) { - mEnabled = false; - updateRequirements(); - } } /** Called on mLooper thread */ - public void setRequest(ProviderRequestUnbundled request, WorkSource source) { + public void setRequest(ProviderRequestUnbundled request) { mRequest = request; - mEnabled = request.getReportLocation(); updateRequirements(); } @@ -120,6 +82,7 @@ public class FusionEngine implements LocationListener { public boolean requested; public long requestTime; public long minTime; + @Override public String toString() { return (requested ? " REQUESTED" : " ---"); @@ -154,7 +117,7 @@ public class FusionEngine implements LocationListener { } private void updateRequirements() { - if (!mEnabled || mRequest == null) { + if (mRequest == null || !mRequest.getReportLocation()) { mRequest = null; disableProvider(NETWORK); disableProvider(GPS); @@ -200,29 +163,30 @@ public class FusionEngine implements LocationListener { * Test whether one location (a) is better to use than another (b). */ private static boolean isBetterThan(Location locationA, Location locationB) { - if (locationA == null) { - return false; - } - if (locationB == null) { - return true; - } - // A provider is better if the reading is sufficiently newer. Heading - // underground can cause GPS to stop reporting fixes. In this case it's - // appropriate to revert to cell, even when its accuracy is less. - if (locationA.getElapsedRealtimeNanos() > locationB.getElapsedRealtimeNanos() + SWITCH_ON_FRESHNESS_CLIFF_NS) { - return true; - } - - // A provider is better if it has better accuracy. Assuming both readings - // are fresh (and by that accurate), choose the one with the smaller - // accuracy circle. - if (!locationA.hasAccuracy()) { - return false; - } - if (!locationB.hasAccuracy()) { - return true; - } - return locationA.getAccuracy() < locationB.getAccuracy(); + if (locationA == null) { + return false; + } + if (locationB == null) { + return true; + } + // A provider is better if the reading is sufficiently newer. Heading + // underground can cause GPS to stop reporting fixes. In this case it's + // appropriate to revert to cell, even when its accuracy is less. + if (locationA.getElapsedRealtimeNanos() + > locationB.getElapsedRealtimeNanos() + SWITCH_ON_FRESHNESS_CLIFF_NS) { + return true; + } + + // A provider is better if it has better accuracy. Assuming both readings + // are fresh (and by that accurate), choose the one with the smaller + // accuracy circle. + if (!locationA.hasAccuracy()) { + return false; + } + if (!locationB.hasAccuracy()) { + return true; + } + return locationA.getAccuracy() < locationB.getAccuracy(); } private void updateFusedLocation() { @@ -252,9 +216,9 @@ public class FusionEngine implements LocationListener { } if (mCallback != null) { - mCallback.reportLocation(mFusedLocation); + mCallback.reportLocation(mFusedLocation); } else { - Log.w(TAG, "Location updates received while fusion engine not started"); + Log.w(TAG, "Location updates received while fusion engine not started"); } } @@ -272,19 +236,22 @@ public class FusionEngine implements LocationListener { /** Called on mLooper thread */ @Override - public void onStatusChanged(String provider, int status, Bundle extras) { } + public void onStatusChanged(String provider, int status, Bundle extras) { + } /** Called on mLooper thread */ @Override - public void onProviderEnabled(String provider) { } + public void onProviderEnabled(String provider) { + } /** Called on mLooper thread */ @Override - public void onProviderDisabled(String provider) { } + public void onProviderDisabled(String provider) { + } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { StringBuilder s = new StringBuilder(); - s.append("mEnabled=").append(mEnabled).append(' ').append(mRequest).append('\n'); + s.append(mRequest).append('\n'); s.append("fused=").append(mFusedLocation).append('\n'); s.append(String.format("gps %s\n", mGpsLocation)); s.append(" ").append(mStats.get(GPS)).append('\n'); diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java index 8f254e9735da..a7de631cd6fa 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java @@ -200,7 +200,7 @@ class MtpManager { } if (objectInfo.getFormat() != MtpConstants.FORMAT_ASSOCIATION) { if (!device.sendObject(sendObjectInfoResult.getObjectHandle(), - sendObjectInfoResult.getCompressedSize(), source)) { + sendObjectInfoResult.getCompressedSizeLong(), source)) { throw new IOException("Failed to send contents of a document"); } } diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index eb9ec82c9519..18b86628ea4d 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -15,6 +15,7 @@ <uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" /> + <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" /> <uses-permission android:name="com.google.android.permission.INSTALL_WEARABLE_PACKAGES" /> diff --git a/packages/PackageInstaller/res/layout/uninstall_content_view.xml b/packages/PackageInstaller/res/layout/uninstall_content_view.xml new file mode 100644 index 000000000000..5ecb614e7209 --- /dev/null +++ b/packages/PackageInstaller/res/layout/uninstall_content_view.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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. + --> + +<!-- Check box that is displayed in the activity resolver UI for the user + to make their selection the preferred activity. --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="?android:attr/alertDialogTheme" + android:orientation="vertical" + android:paddingTop="8dp" + android:paddingStart="?android:attr/dialogPreferredPadding" + android:paddingEnd="?android:attr/dialogPreferredPadding" + android:clipToPadding="false"> + + <TextView + android:id="@+id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.Material.Subhead" /> + + <CheckBox + android:id="@+id/checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginStart="-8dp" + android:paddingLeft="8sp" + style="@android:style/TextAppearance.Material.Subhead" /> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/PackageInstaller/res/values-mr/strings.xml b/packages/PackageInstaller/res/values-mr/strings.xml index d751c42a2df7..01f3e5b5bd82 100644 --- a/packages/PackageInstaller/res/values-mr/strings.xml +++ b/packages/PackageInstaller/res/values-mr/strings.xml @@ -22,17 +22,17 @@ <string name="cancel" msgid="1018267193425558088">"रद्द करा"</string> <string name="installing" msgid="4921993079741206516">"इंस्टॉल होत आहे…"</string> <string name="installing_app" msgid="1165095864863849422">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> इंस्टॉल होत आहे…"</string> - <string name="install_done" msgid="5987363587661783896">"अॅप इंस्टॉल झाले."</string> + <string name="install_done" msgid="5987363587661783896">"अॅप इंस्टॉल झाले."</string> <string name="install_confirm_question" msgid="8176284075816604590">"तुम्हाला हे अॅप्लिकेशन इंस्टॉल करायचे आहे का?"</string> <string name="install_confirm_question_update" msgid="7942235418781274635">"तुम्हाच्या विद्यमान अॅप्लिकेशनवर अपडेट इंस्टॉल करायचे आहे का? तुमचा विद्यमान डेटा गमावणार नाही."</string> <string name="install_confirm_question_update_system" msgid="4713001702777910263">"तुम्हाला या बिल्ट-इन अॅप्लिकेशनवर अपडेट इंस्टॉल करायचे आहे का? तुमचा विद्यमान डेटा गमावणार नाही."</string> - <string name="install_failed" msgid="5777824004474125469">"अॅप इंस्टॉल झाले नाही."</string> + <string name="install_failed" msgid="5777824004474125469">"अॅप इंस्टॉल झाले नाही."</string> <string name="install_failed_blocked" msgid="8512284352994752094">"पॅकेज इंस्टॉल होण्यापासून ब्लॉक केले होते."</string> - <string name="install_failed_conflict" msgid="3493184212162521426">"पॅकेजचा विद्यमान पॅकेजशी विरोध असल्याने अॅप इंस्टॉल झाले नाही."</string> - <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"तुमच्या टॅबलेटशी कंपॅटिबल नसल्याने अॅप इंस्टॉल झाले नाही."</string> - <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"हे अॅप तुमच्या टीव्हीशी कंपॅटिबल नाही."</string> - <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"तुमच्या फोनशी कंपॅटिबल नसल्याने अॅप इंस्टॉल झाले नाही."</string> - <string name="install_failed_invalid_apk" msgid="8581007676422623930">"पॅकेज अयोग्य असल्याचे दिसत असल्याने अॅप इंस्टॉल झाले नाही."</string> + <string name="install_failed_conflict" msgid="3493184212162521426">"पॅकेजचा विद्यमान पॅकेजशी विरोध असल्याने अॅप इंस्टॉल झाले नाही."</string> + <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"तुमच्या टॅबलेटशी कंपॅटिबल नसल्याने अॅप इंस्टॉल झाले नाही."</string> + <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"हे अॅप तुमच्या टीव्हीशी कंपॅटिबल नाही."</string> + <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"तुमच्या फोनशी कंपॅटिबल नसल्याने अॅप इंस्टॉल झाले नाही."</string> + <string name="install_failed_invalid_apk" msgid="8581007676422623930">"पॅकेज अयोग्य असल्याचे दिसत असल्याने अॅप इंस्टॉल झाले नाही."</string> <string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"<xliff:g id="APP_NAME">%1$s</xliff:g> तुमच्या टॅबलेटवर इंस्टॉल केले जाऊ शकत नाही."</string> <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"<xliff:g id="APP_NAME">%1$s</xliff:g> तुमच्या टीव्हीवर इंस्टॉल केले जाऊ शकत नाही."</string> <string name="install_failed_msg" product="default" msgid="6484461562647915707">"<xliff:g id="APP_NAME">%1$s</xliff:g> तुमच्या फोनवर इंस्टॉल केले जाऊ शकत नाही."</string> @@ -44,20 +44,20 @@ <string name="manage_applications" msgid="5400164782453975580">"अॅप्स व्यवस्थापन"</string> <string name="out_of_space_dlg_title" msgid="4156690013884649502">"जागा संपली"</string> <string name="out_of_space_dlg_text" msgid="8727714096031856231">"<xliff:g id="APP_NAME">%1$s</xliff:g> इंस्टॉल केले जाऊ शकत नाही. काही जागा मोकळी करा आणि पुन्हा प्रयत्न करा."</string> - <string name="app_not_found_dlg_title" msgid="5107924008597470285">"अॅप आढळले नाही"</string> - <string name="app_not_found_dlg_text" msgid="5219983779377811611">"इंस्टॉल केलेल्या अॅप्सच्या सूचीमध्ये अॅप आढळले नाही."</string> + <string name="app_not_found_dlg_title" msgid="5107924008597470285">"अॅप आढळले नाही"</string> + <string name="app_not_found_dlg_text" msgid="5219983779377811611">"इंस्टॉल केलेल्या अॅप्सच्या सूचीमध्ये अॅप आढळले नाही."</string> <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"अनुमती नाही"</string> <string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"हे अनइंस्टॉल करण्याची विद्यमान वापरकर्त्यास अनुमती नाही."</string> <string name="generic_error_dlg_title" msgid="5863195085927067752">"एरर"</string> - <string name="generic_error_dlg_text" msgid="5287861443265795232">"अॅप अनइंस्टॉल करणे शक्य झाले नाही."</string> - <string name="uninstall_application_title" msgid="4045420072401428123">"अॅप अनइंस्टॉल करा"</string> + <string name="generic_error_dlg_text" msgid="5287861443265795232">"अॅप अनइंस्टॉल करणे शक्य झाले नाही."</string> + <string name="uninstall_application_title" msgid="4045420072401428123">"अॅप अनइंस्टॉल करा"</string> <string name="uninstall_update_title" msgid="824411791011583031">"अपडेट अनइंस्टॉल करा"</string> <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> खालील अॅपचा भाग आहे:"</string> - <string name="uninstall_application_text" msgid="3816830743706143980">"तुम्हाला हे अॅप अनइंस्टॉल करायचे आहे का?"</string> - <string name="uninstall_application_text_all_users" msgid="575491774380227119">"तुम्हाला हे अॅप "<b>"सर्व"</b>" वापरकर्त्यांसाठी अनइंस्टॉल करायचे आहे का? अॅप्लिकेशन आणि त्याचा डेटा डिव्हाइसवरील "<b>"सर्व"</b>" वापरकर्त्यांकडून काढला जाईल."</string> + <string name="uninstall_application_text" msgid="3816830743706143980">"तुम्हाला हे अॅप अनइंस्टॉल करायचे आहे का?"</string> + <string name="uninstall_application_text_all_users" msgid="575491774380227119">"तुम्हाला हे अॅप "<b>"सर्व"</b>" वापरकर्त्यांसाठी अनइंस्टॉल करायचे आहे का? अॅप्लिकेशन आणि त्याचा डेटा डिव्हाइसवरील "<b>"सर्व"</b>" वापरकर्त्यांकडून काढला जाईल."</string> <string name="uninstall_application_text_user" msgid="498072714173920526">"तुम्हाला <xliff:g id="USERNAME">%1$s</xliff:g> वापरकर्त्यासाठी हे अॅप अनइंस्टॉल करायचे आहे का?"</string> - <string name="uninstall_update_text" msgid="863648314632448705">"फॅक्टरी आवृत्तीसह हे अॅप बदलायचे का? सर्व डेटा काढला जाईल."</string> - <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"फॅक्टरी आवृत्तीसह हे अॅप बदलायचे? सर्व डेटा काढला जाईल. हे कार्य प्रोफाइल असलेल्यांसह या डिव्हाइसच्या सर्व वापरकर्त्यांना प्रभावित करते."</string> + <string name="uninstall_update_text" msgid="863648314632448705">"फॅक्टरी आवृत्तीसह हे अॅप बदलायचे का? सर्व डेटा काढला जाईल."</string> + <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"फॅक्टरी आवृत्तीसह हे अॅप बदलायचे? सर्व डेटा काढला जाईल. हे कार्य प्रोफाइल असलेल्यांसह या डिव्हाइसच्या सर्व वापरकर्त्यांना प्रभावित करते."</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"अनइंस्टॉल रन होत आहेत"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"अनइंस्टॉल करता आले नाही"</string> <string name="uninstalling" msgid="8709566347688966845">"अनइंस्टॉल करत आहे…"</string> @@ -68,7 +68,7 @@ <string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> अनइंस्टॉल करता आले नाही."</string> <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"अॅक्टिव्ह डिव्हाइस प्रशासक अॅप अनइंस्टॉल करू शकत नाही"</string> <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g> साठी अॅक्टिव्ह डिव्हाइस प्रशासक अॅप अनइंस्टॉल करू शकत नाही"</string> - <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"हे अॅप काही वापरकर्ते किंवा प्रोफाइलसाठी आवश्यक आहे आणि इतरांसाठी अनइंस्टॉल करण्यात आले"</string> + <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"हे अॅप काही वापरकर्ते किंवा प्रोफाइलसाठी आवश्यक आहे आणि इतरांसाठी अनइंस्टॉल करण्यात आले"</string> <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"तुमच्या प्रोफाइलसाठी हे अॅप आवश्यक आहे आणि अनइंस्टॉल केले जाऊ शकत नाही."</string> <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"तुमच्या डिव्हाइस प्रशासकास हे अॅप आवश्यक आहे आणि ते अनइंस्टॉल केले जाऊ शकत नाही."</string> <string name="manage_device_administrators" msgid="3092696419363842816">"डिव्हाइस प्रशासक अॅप्स व्यवस्थापित करा"</string> @@ -77,18 +77,18 @@ <string name="Parse_error_dlg_text" msgid="1661404001063076789">"पॅकेज पार्स करण्यात समस्या आली."</string> <string name="wear_not_allowed_dlg_title" msgid="8664785993465117517">"Android Wear"</string> <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"इंस्टॉल करा/अनइंस्टॉल करा क्रिया Wear वर सपोर्ट करत नाहीत."</string> - <string name="message_staging" msgid="8032722385658438567">"अॅप सुरुवातीच्या स्थितीत आहे…"</string> + <string name="message_staging" msgid="8032722385658438567">"अॅप सुरुवातीच्या स्थितीत आहे…"</string> <string name="app_name_unknown" msgid="6881210203354323926">"अज्ञात"</string> <string name="untrusted_external_source_warning" product="tablet" msgid="6539403649459942547">"तुमच्या सुरक्षिततेसाठी, तुमच्या टॅबलेटला या स्रोताकडील अज्ञात अॅप्स इंस्टॉल करण्याची अनुमती नाही."</string> <string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"तुमच्या सुरक्षिततेसाठी, तुमच्या टीव्हीला या स्रोताकडील अज्ञात अॅप्स इंस्टॉल करण्याची अनुमती नाही."</string> <string name="untrusted_external_source_warning" product="default" msgid="7279739265754475165">"तुमच्या सुरक्षिततेसाठी, तुमच्या फोनला या स्रोताकडील अज्ञात अॅप्स इंस्टॉल करण्याची अनुमती नाही."</string> - <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"तुमचा फोन आणि वैयक्तिक डेटा अज्ञात अॅप्सकडून होणार्या अटॅकमुळे अधिक असुरक्षित आहे. हे अॅप इंस्टॉल करून, तुम्ही सहमती देता की ते वापरल्याने होणार्या तुमच्या फोनचे कोणत्याही प्रकारे होणारे नुकसान किंवा डेटा हानीसाठी तुम्ही जबाबदार आहात."</string> - <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"तुमचा टॅबलेट आणि वैयक्तिक डेटा अज्ञात अॅप्सकडून होणार्या अटॅकमुळे अधिक असुरक्षित आहे. हे अॅप इंस्टॉल करून, तुम्ही सहमती देता की ते वापरल्याने तुमच्या टॅबलेटचे कोणत्याही प्रकारे होणारे नुकसान किंवा डेटा हानीसाठी तुम्ही जबाबदार आहात."</string> - <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"तुमचा टीव्ही आणि वैयक्तिक डेटा अज्ञात अॅप्सकडून होणार्या अटॅकमुळे अधिक असुरक्षित आहे. हे अॅप इंस्टॉल करून, तुम्ही सहमती देता की ते वापरल्याने तुमच्या टीव्हीचे कोणत्याही प्रकारे होणारे नुकसान किंवा डेटा हानीसाठी तुम्ही जबाबदार आहात."</string> + <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"तुमचा फोन आणि वैयक्तिक डेटा अज्ञात अॅप्सकडून होणार्या अटॅकमुळे अधिक असुरक्षित आहे. हे अॅप इंस्टॉल करून, तुम्ही सहमती देता की ते वापरल्याने होणार्या तुमच्या फोनचे कोणत्याही प्रकारे होणारे नुकसान किंवा डेटा हानीसाठी तुम्ही जबाबदार आहात."</string> + <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"तुमचा टॅबलेट आणि वैयक्तिक डेटा अज्ञात अॅप्सकडून होणार्या अटॅकमुळे अधिक असुरक्षित आहे. हे अॅप इंस्टॉल करून, तुम्ही सहमती देता की ते वापरल्याने तुमच्या टॅबलेटचे कोणत्याही प्रकारे होणारे नुकसान किंवा डेटा हानीसाठी तुम्ही जबाबदार आहात."</string> + <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"तुमचा टीव्ही आणि वैयक्तिक डेटा अज्ञात अॅप्सकडून होणार्या अटॅकमुळे अधिक असुरक्षित आहे. हे अॅप इंस्टॉल करून, तुम्ही सहमती देता की ते वापरल्याने तुमच्या टीव्हीचे कोणत्याही प्रकारे होणारे नुकसान किंवा डेटा हानीसाठी तुम्ही जबाबदार आहात."</string> <string name="anonymous_source_continue" msgid="4375745439457209366">"सुरू ठेवा"</string> <string name="external_sources_settings" msgid="4046964413071713807">"सेटिंग्ज"</string> <string name="wear_app_channel" msgid="1960809674709107850">"wear अॅप्स इंस्टॉल/अनइंस्टॉल करत आहे"</string> - <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"अॅप इंस्टॉल सूचना"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"अॅप इंस्टॉल सूचना"</string> <string name="notification_installation_success_message" msgid="6450467996056038442">"यशस्वीरित्या इंस्टॉल केले"</string> <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" यशस्वीरित्या इंस्टॉल झाले"</string> </resources> diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml index 0f065ab95e2d..1d8747a1fda6 100644 --- a/packages/PackageInstaller/res/values/strings.xml +++ b/packages/PackageInstaller/res/values/strings.xml @@ -119,6 +119,8 @@ <string name="uninstall_update_text">Replace this app with the factory version? All data will be removed.</string> <!-- [CHAR LIMIT=none] --> <string name="uninstall_update_text_multiuser">Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles.</string> + <!-- Label of a checkbox that allows to remove the files contributed by app during uninstall [CHAR LIMIT=none] --> + <string name="uninstall_remove_contributed_files">Also remove <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of associated media files.</string> <!-- Label for the notification channel containing notifications for current uninstall operations [CHAR LIMIT=40] --> <string name="uninstalling_notification_channel">Running uninstalls</string> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java index 1c0aec18dd30..d13bb65604af 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java @@ -50,6 +50,8 @@ public class UninstallUninstalling extends Activity implements "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT"; static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL"; + static final String EXTRA_CLEAR_CONTRIBUTED_FILES = + "com.android.packageinstaller.extra.CLEAR_CONTRIBUTED_FILES"; private int mUninstallId; private ApplicationInfo mAppInfo; @@ -72,6 +74,8 @@ public class UninstallUninstalling extends Activity implements if (savedInstanceState == null) { boolean allUsers = getIntent().getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); + boolean clearContributedFiles = getIntent().getBooleanExtra( + EXTRA_CLEAR_CONTRIBUTED_FILES, false); UserHandle user = getIntent().getParcelableExtra(Intent.EXTRA_USER); // Show dialog, which is the whole UI @@ -95,12 +99,15 @@ public class UninstallUninstalling extends Activity implements PendingIntent pendingIntent = PendingIntent.getBroadcast(this, mUninstallId, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT); + int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0; + flags |= clearContributedFiles ? PackageManager.DELETE_CONTRIBUTED_MEDIA : 0; + try { ActivityThread.getPackageManager().getPackageInstaller().uninstall( new VersionedPackage(mAppInfo.packageName, PackageManager.VERSION_CODE_HIGHEST), - getPackageName(), allUsers ? PackageManager.DELETE_ALL_USERS : 0, - pendingIntent.getIntentSender(), user.getIdentifier()); + getPackageName(), flags, pendingIntent.getIntentSender(), + user.getIdentifier()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java index 1a01dc019bf7..0fa8c9a688c7 100755 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java @@ -285,7 +285,7 @@ public class UninstallerActivity extends Activity { fragment.show(ft, "dialog"); } - public void startUninstallProgress() { + public void startUninstallProgress(boolean clearContributedFiles) { boolean returnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); CharSequence label = mDialogInfo.appInfo.loadSafeLabel(getPackageManager()); @@ -310,6 +310,8 @@ public class UninstallerActivity extends Activity { newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers); newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo); newIntent.putExtra(UninstallUninstalling.EXTRA_APP_LABEL, label); + newIntent.putExtra(UninstallUninstalling.EXTRA_CLEAR_CONTRIBUTED_FILES, + clearContributedFiles); newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback); if (returnResult) { @@ -358,12 +360,14 @@ public class UninstallerActivity extends Activity { try { Log.i(TAG, "Uninstalling extras=" + broadcastIntent.getExtras()); + int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0; + flags |= clearContributedFiles ? PackageManager.DELETE_CONTRIBUTED_MEDIA : 0; + ActivityThread.getPackageManager().getPackageInstaller().uninstall( new VersionedPackage(mDialogInfo.appInfo.packageName, PackageManager.VERSION_CODE_HIGHEST), - getPackageName(), mDialogInfo.allUsers - ? PackageManager.DELETE_ALL_USERS : 0, - pendingIntent.getIntentSender(), mDialogInfo.user.getIdentifier()); + getPackageName(), flags, pendingIntent.getIntentSender(), + mDialogInfo.user.getIdentifier()); } catch (Exception e) { notificationManager.cancel(uninstallId); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java index e0ca74eb99c9..e4e12759211d 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java @@ -16,6 +16,9 @@ package com.android.packageinstaller.handheld; +import static android.text.format.Formatter.formatFileSize; + +import android.annotation.NonNull; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -24,14 +27,41 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.Bundle; +import android.os.UserHandle; import android.os.UserManager; +import android.provider.MediaStore; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; import com.android.packageinstaller.R; import com.android.packageinstaller.UninstallerActivity; +import java.io.IOException; +import java.util.List; + public class UninstallAlertDialogFragment extends DialogFragment implements DialogInterface.OnClickListener { + private CheckBox mClearContributedFiles; + + /** + * Get number of bytes of the combined files contributed by the package. + * + * @param pkg The package that might have contibuted files. + * @param user The user the package belongs to. + * + * @return The number of bytes. + */ + private long getContributedMediaSize(@NonNull String pkg, @NonNull UserHandle user) { + try { + return MediaStore.getContributedMediaSize(getContext(), pkg, user); + } catch (IOException e) { + return 0; + } + } + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final PackageManager pm = getActivity().getPackageManager(); @@ -76,14 +106,44 @@ public class UninstallAlertDialogFragment extends DialogFragment implements dialogBuilder.setTitle(appLabel); dialogBuilder.setPositiveButton(android.R.string.ok, this); dialogBuilder.setNegativeButton(android.R.string.cancel, this); - dialogBuilder.setMessage(messageBuilder.toString()); + + String pkg = dialogInfo.appInfo.packageName; + long contributedFileSize = 0; + if (dialogInfo.allUsers) { + List<UserInfo> users = userManager.getUsers(); + + int numUsers = users.size(); + for (int i = 0; i < numUsers; i++) { + UserHandle user = UserHandle.of(users.get(i).id); + + contributedFileSize += getContributedMediaSize(pkg, user); + } + } else { + contributedFileSize = getContributedMediaSize(pkg, dialogInfo.user); + } + + if (contributedFileSize == 0) { + dialogBuilder.setMessage(messageBuilder.toString()); + } else { + LayoutInflater inflater = getContext().getSystemService(LayoutInflater.class); + ViewGroup content = (ViewGroup) inflater.inflate(R.layout.uninstall_content_view, null); + + ((TextView) content.requireViewById(R.id.message)).setText(messageBuilder.toString()); + mClearContributedFiles = content.requireViewById(R.id.checkbox); + mClearContributedFiles.setText(getString(R.string.uninstall_remove_contributed_files, + formatFileSize(getContext(), contributedFileSize))); + + dialogBuilder.setView(content); + } + return dialogBuilder.create(); } @Override public void onClick(DialogInterface dialog, int which) { if (which == Dialog.BUTTON_POSITIVE) { - ((UninstallerActivity) getActivity()).startUninstallProgress(); + ((UninstallerActivity) getActivity()).startUninstallProgress( + mClearContributedFiles != null && mClearContributedFiles.isChecked()); } else { ((UninstallerActivity) getActivity()).dispatchAborted(); } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java index 828e5dbcfe5b..21d25f5b030f 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java @@ -99,7 +99,7 @@ public class UninstallAlertFragment extends GuidedStepFragment { public void onGuidedActionClicked(GuidedAction action) { if (isAdded()) { if (action.getId() == GuidedAction.ACTION_ID_OK) { - ((UninstallerActivity) getActivity()).startUninstallProgress(); + ((UninstallerActivity) getActivity()).startUninstallProgress(false); getActivity().finish(); } else { ((UninstallerActivity) getActivity()).dispatchAborted(); diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml index 03eafc4ab836..478ee54b40e5 100644 --- a/packages/PrintSpooler/AndroidManifest.xml +++ b/packages/PrintSpooler/AndroidManifest.xml @@ -51,6 +51,7 @@ <application android:allowClearUserData="true" android:label="@string/app_label" + android:icon="@drawable/ic_app_icon" android:supportsRtl="true"> <service diff --git a/packages/PrintSpooler/res/drawable/app_icon_foreground.xml b/packages/PrintSpooler/res/drawable/app_icon_foreground.xml new file mode 100644 index 000000000000..249e387d7f9f --- /dev/null +++ b/packages/PrintSpooler/res/drawable/app_icon_foreground.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 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. + --> +<inset + xmlns:android="http://schemas.android.com/apk/res/android" + android:insetTop="25%" + android:insetRight="25%" + android:insetBottom="25%" + android:insetLeft="25%"> + + <vector + android:width="36dp" + android:height="36dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="#FFFFFF" + android:pathData="M19,8L5,8c-1.66,0 -3,1.34 -3,3v6h4v4h12v-4h4v-6c0,-1.66 -1.34,-3 -3,-3zM16,19L8,19v-5h8v5zM19,12c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,3L6,3v4h12L18,3z" /> + </vector> +</inset> diff --git a/packages/PrintSpooler/res/drawable/ic_app_icon.xml b/packages/PrintSpooler/res/drawable/ic_app_icon.xml new file mode 100644 index 000000000000..82c18e07f5c4 --- /dev/null +++ b/packages/PrintSpooler/res/drawable/ic_app_icon.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 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. + --> + +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@*android:color/accent_device_default_light"/> + <foreground android:drawable="@drawable/app_icon_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index cc17b25d9a40..0126e7e59915 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -18,6 +18,7 @@ android_library { "SettingsLibSettingsSpinner", "SettingsLayoutPreference", "ActionButtonsPreference", + "SettingsLibEntityHeaderWidgets", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/EntityHeaderWidgets/Android.bp b/packages/SettingsLib/EntityHeaderWidgets/Android.bp new file mode 100644 index 000000000000..3ca4ecd33ce4 --- /dev/null +++ b/packages/SettingsLib/EntityHeaderWidgets/Android.bp @@ -0,0 +1,14 @@ +android_library { + name: "SettingsLibEntityHeaderWidgets", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + static_libs: [ + "androidx.annotation_annotation", + "SettingsLibAppPreference" + ], + + sdk_version: "system_current", + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/EntityHeaderWidgets/AndroidManifest.xml b/packages/SettingsLib/EntityHeaderWidgets/AndroidManifest.xml new file mode 100644 index 000000000000..4b9f1ab8d6cc --- /dev/null +++ b/packages/SettingsLib/EntityHeaderWidgets/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.widget"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml new file mode 100644 index 000000000000..9f30eda242f6 --- /dev/null +++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 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/app_entities_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="24dp" + android:paddingEnd="8dp" + android:gravity="center" + android:orientation="vertical"> + + <TextView + android:id="@+id/header_title" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:gravity="center" + android:textAppearance="@style/AppEntitiesHeader.Text.HeaderTitle"/> + + <LinearLayout + android:id="@+id/all_apps_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="16dp" + android:paddingBottom="16dp" + android:gravity="center"> + + <include + android:id="@+id/app1_view" + layout="@layout/app_view"/> + + <include + android:id="@+id/app2_view" + layout="@layout/app_view"/> + + <include + android:id="@+id/app3_view" + layout="@layout/app_view"/> + + </LinearLayout> + + <Button + android:id="@+id/header_details" + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:gravity="center"/> + +</LinearLayout> diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml new file mode 100644 index 000000000000..fcafa3140955 --- /dev/null +++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_view.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 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="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginEnd="16dp" + android:gravity="center" + android:orientation="vertical"> + + <ImageView + android:id="@+id/app_icon" + android:layout_width="@dimen/secondary_app_icon_size" + android:layout_height="@dimen/secondary_app_icon_size" + android:layout_marginBottom="12dp"/> + + <TextView + android:id="@+id/app_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="2dp" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="@style/AppEntitiesHeader.Text.Title"/> + + <TextView + android:id="@+id/app_summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="@style/AppEntitiesHeader.Text.Summary"/> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/values/styles.xml b/packages/SettingsLib/EntityHeaderWidgets/res/values/styles.xml new file mode 100644 index 000000000000..0eefd4bff97f --- /dev/null +++ b/packages/SettingsLib/EntityHeaderWidgets/res/values/styles.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + <style name="AppEntitiesHeader.Text" + parent="@android:style/TextAppearance.Material.Subhead"> + <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + + <style name="AppEntitiesHeader.Text.HeaderTitle"> + <item name="android:textSize">14sp</item> + </style> + + <style name="AppEntitiesHeader.Text.Title"> + <item name="android:textSize">16sp</item> + </style> + + <style name="AppEntitiesHeader.Text.Summary" + parent="@android:style/TextAppearance.Material.Body1"> + <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textSize">14sp</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java new file mode 100644 index 000000000000..8ccf89fc38b0 --- /dev/null +++ b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.widget; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.annotation.VisibleForTesting; + +/** + * This is used to initialize view which was inflated + * from {@link R.xml.app_entities_header.xml}. + * + * <p>The view looks like below. + * + * <pre> + * -------------------------------------------------------------- + * | Header title | + * -------------------------------------------------------------- + * | App1 icon | App2 icon | App3 icon | + * | App1 title | App2 title | App3 title | + * | App1 summary | App2 summary | App3 summary | + * |------------------------------------------------------------- + * | Header details | + * -------------------------------------------------------------- + * </pre> + * + * <p>How to use AppEntitiesHeaderController? + * + * <p>1. Add a {@link LayoutPreference} in layout XML file. + * <pre> + * <com.android.settingslib.widget.LayoutPreference + * android:key="app_entities_header" + * android:layout="@layout/app_entities_header"/> + * </pre> + * + * <p>2. Use AppEntitiesHeaderController to call below methods, then you can initialize + * view of <code>app_entities_header</code>. + * + * <pre> + * + * View headerView = ((LayoutPreference) screen.findPreference("app_entities_header")) + * .findViewById(R.id.app_entities_header); + * + * AppEntitiesHeaderController.newInstance(context, headerView) + * .setHeaderTitleRes(R.string.xxxxx) + * .setHeaderDetailsRes(R.string.xxxxx) + * .setHeaderDetailsClickListener(onClickListener) + * .setAppEntity(0, icon, "app title", "app summary") + * .setAppEntity(1, icon, "app title", "app summary") + * .setAppEntity(2, icon, "app title", "app summary") + * .apply(); + * </pre> + */ +public class AppEntitiesHeaderController { + + private static final String TAG = "AppEntitiesHeaderCtl"; + + @VisibleForTesting + static final int MAXIMUM_APPS = 3; + + private final Context mContext; + private final TextView mHeaderTitleView; + private final Button mHeaderDetailsView; + + private final AppEntity[] mAppEntities; + private final View[] mAppEntityViews; + private final ImageView[] mAppIconViews; + private final TextView[] mAppTitleViews; + private final TextView[] mAppSummaryViews; + + private int mHeaderTitleRes; + private int mHeaderDetailsRes; + private View.OnClickListener mDetailsOnClickListener; + + /** + * Creates a new instance of the controller. + * + * @param context the Context the view is running in + * @param appEntitiesHeaderView view was inflated from <code>app_entities_header</code> + */ + public static AppEntitiesHeaderController newInstance(@NonNull Context context, + @NonNull View appEntitiesHeaderView) { + return new AppEntitiesHeaderController(context, appEntitiesHeaderView); + } + + private AppEntitiesHeaderController(Context context, View appEntitiesHeaderView) { + mContext = context; + mHeaderTitleView = appEntitiesHeaderView.findViewById(R.id.header_title); + mHeaderDetailsView = appEntitiesHeaderView.findViewById(R.id.header_details); + + mAppEntities = new AppEntity[MAXIMUM_APPS]; + mAppIconViews = new ImageView[MAXIMUM_APPS]; + mAppTitleViews = new TextView[MAXIMUM_APPS]; + mAppSummaryViews = new TextView[MAXIMUM_APPS]; + + mAppEntityViews = new View[]{ + appEntitiesHeaderView.findViewById(R.id.app1_view), + appEntitiesHeaderView.findViewById(R.id.app2_view), + appEntitiesHeaderView.findViewById(R.id.app3_view) + }; + + // Initialize view in advance, so we won't take too much time to do it when controller is + // binding view. + for (int index = 0; index < MAXIMUM_APPS; index++) { + final View appView = mAppEntityViews[index]; + mAppIconViews[index] = (ImageView) appView.findViewById(R.id.app_icon); + mAppTitleViews[index] = (TextView) appView.findViewById(R.id.app_title); + mAppSummaryViews[index] = (TextView) appView.findViewById(R.id.app_summary); + } + } + + /** + * Set the text resource for app entities header title. + */ + public AppEntitiesHeaderController setHeaderTitleRes(@StringRes int titleRes) { + mHeaderTitleRes = titleRes; + return this; + } + + /** + * Set the text resource for app entities header details. + */ + public AppEntitiesHeaderController setHeaderDetailsRes(@StringRes int detailsRes) { + mHeaderDetailsRes = detailsRes; + return this; + } + + /** + * Register a callback to be invoked when header details view is clicked. + */ + public AppEntitiesHeaderController setHeaderDetailsClickListener( + @Nullable View.OnClickListener clickListener) { + mDetailsOnClickListener = clickListener; + return this; + } + + /** + * Set an app entity at a specified position view. + * + * @param index the index at which the specified view is to be inserted + * @param icon the icon of app entity + * @param titleRes the title of app entity + * @param summaryRes the summary of app entity + * @return this {@code AppEntitiesHeaderController} object + */ + public AppEntitiesHeaderController setAppEntity(int index, @NonNull Drawable icon, + @Nullable CharSequence titleRes, @Nullable CharSequence summaryRes) { + final AppEntity appEntity = new AppEntity(icon, titleRes, summaryRes); + mAppEntities[index] = appEntity; + return this; + } + + /** + * Remove an app entity at a specified position view. + * + * @param index the index at which the specified view is to be removed + * @return this {@code AppEntitiesHeaderController} object + */ + public AppEntitiesHeaderController removeAppEntity(int index) { + mAppEntities[index] = null; + return this; + } + + /** + * Clear all app entities in app entities header. + * + * @return this {@code AppEntitiesHeaderController} object + */ + public AppEntitiesHeaderController clearAllAppEntities() { + for (int index = 0; index < MAXIMUM_APPS; index++) { + removeAppEntity(index); + } + return this; + } + + /** + * Done mutating app entities header, rebinds everything. + */ + public void apply() { + bindHeaderTitleView(); + bindHeaderDetailsView(); + + // Rebind all apps view + for (int index = 0; index < MAXIMUM_APPS; index++) { + bindAppEntityView(index); + } + } + + private void bindHeaderTitleView() { + CharSequence titleText = ""; + try { + titleText = mContext.getText(mHeaderTitleRes); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Resource of header title can't not be found!", e); + } + mHeaderTitleView.setText(titleText); + mHeaderTitleView.setVisibility( + TextUtils.isEmpty(titleText) ? View.GONE : View.VISIBLE); + } + + private void bindHeaderDetailsView() { + CharSequence detailsText = ""; + try { + detailsText = mContext.getText(mHeaderDetailsRes); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Resource of header details can't not be found!", e); + } + mHeaderDetailsView.setText(detailsText); + mHeaderDetailsView.setVisibility( + TextUtils.isEmpty(detailsText) ? View.GONE : View.VISIBLE); + mHeaderDetailsView.setOnClickListener(mDetailsOnClickListener); + } + + private void bindAppEntityView(int index) { + final AppEntity appEntity = mAppEntities[index]; + mAppEntityViews[index].setVisibility(appEntity != null ? View.VISIBLE : View.GONE); + + if (appEntity != null) { + mAppIconViews[index].setImageDrawable(appEntity.icon); + + mAppTitleViews[index].setVisibility( + TextUtils.isEmpty(appEntity.title) ? View.INVISIBLE : View.VISIBLE); + mAppTitleViews[index].setText(appEntity.title); + + mAppSummaryViews[index].setVisibility( + TextUtils.isEmpty(appEntity.summary) ? View.INVISIBLE : View.VISIBLE); + mAppSummaryViews[index].setText(appEntity.summary); + } + } + + private static class AppEntity { + public final Drawable icon; + public final CharSequence title; + public final CharSequence summary; + + AppEntity(Drawable appIcon, CharSequence appTitle, CharSequence appSummary) { + icon = appIcon; + title = appTitle; + summary = appSummary; + } + } +} diff --git a/packages/SettingsLib/SearchWidget/res/drawable/ic_search_24dp.xml b/packages/SettingsLib/SearchWidget/res/drawable/ic_search_24dp.xml index 7e65848de189..a046332eaae8 100644 --- a/packages/SettingsLib/SearchWidget/res/drawable/ic_search_24dp.xml +++ b/packages/SettingsLib/SearchWidget/res/drawable/ic_search_24dp.xml @@ -20,7 +20,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?android:attr/colorControlNormal"> + android:tint="?android:attr/colorAccent"> <path android:fillColor="#FF000000" android:pathData="M20.49,19l-5.73,-5.73C15.53,12.2 16,10.91 16,9.5C16,5.91 13.09,3 9.5,3S3,5.91 3,9.5C3,13.09 5.91,16 9.5,16c1.41,0 2.7,-0.47 3.77,-1.24L19,20.49L20.49,19zM5,9.5C5,7.01 7.01,5 9.5,5S14,7.01 14,9.5S11.99,14 9.5,14S5,11.99 5,9.5z"/> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index 8ebb182ac51c..5b09b29e8b05 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -184,7 +184,7 @@ <string name="category_work" msgid="8699184680584175622">"कार्य"</string> <string name="development_settings_title" msgid="215179176067683667">"डेव्हलपर पर्याय"</string> <string name="development_settings_enable" msgid="542530994778109538">"डेव्हलपर पर्याय सुरू करा"</string> - <string name="development_settings_summary" msgid="1815795401632854041">"अॅप विकासासाठी पर्याय सेट करा"</string> + <string name="development_settings_summary" msgid="1815795401632854041">"अॅप विकासासाठी पर्याय सेट करा"</string> <string name="development_settings_not_available" msgid="4308569041701535607">"या वापरकर्त्यासाठी डेव्हलपर पर्याय उपलब्ध नाहीत"</string> <string name="vpn_settings_not_available" msgid="956841430176985598">"या वापरकर्त्यासाठी VPN सेटिंग्ज उपलब्ध नाहीत"</string> <string name="tethering_settings_not_available" msgid="6765770438438291012">"या वापरकर्त्यासाठी टेदरिंग सेटिंग्ज उपलब्ध नाहीत"</string> @@ -202,9 +202,9 @@ <string name="oem_unlock_enable_summary" msgid="4720281828891618376">"बूटलोडर अनलॉक करण्यासाठी अनुमती द्या"</string> <string name="confirm_enable_oem_unlock_title" msgid="4802157344812385674">"OEM अनलॉक करण्यास अनुमती द्यायची?"</string> <string name="confirm_enable_oem_unlock_text" msgid="5517144575601647022">"चेतावणी: हे सेटिंग चालू असताना या डिव्हाइस वर डिव्हाइस संरक्षण वैशिष्ट्ये काम करणार नाहीत."</string> - <string name="mock_location_app" msgid="7966220972812881854">"बनावट स्थान अॅप निवडा"</string> - <string name="mock_location_app_not_set" msgid="809543285495344223">"कोणताही बनावट स्थान अॅप सेट केला नाही"</string> - <string name="mock_location_app_set" msgid="8966420655295102685">"बनावट स्थान अॅप: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="mock_location_app" msgid="7966220972812881854">"बनावट स्थान अॅप निवडा"</string> + <string name="mock_location_app_not_set" msgid="809543285495344223">"कोणताही बनावट स्थान अॅप सेट केला नाही"</string> + <string name="mock_location_app_set" msgid="8966420655295102685">"बनावट स्थान अॅप: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="debug_networking_category" msgid="7044075693643009662">"नेटवर्किंग"</string> <string name="wifi_display_certification" msgid="8611569543791307533">"वायरलेस डिस्प्ले प्रमाणीकरण"</string> <string name="wifi_verbose_logging" msgid="4203729756047242344">"वाय-फाय व्हर्बोझ लॉगिंग सुरू करा"</string> @@ -261,11 +261,11 @@ <string name="bluetooth_show_devices_without_names_summary" msgid="2351196058115755520">"नावांशिवाय ब्लूटूथ डीव्हाइस (फक्त MAC पत्ते) दाखवले जातील"</string> <string name="bluetooth_disable_absolute_volume_summary" msgid="6031284410786545957">"रिमोट डिव्हाइसमध्ये सहन न होणारा मोठा आवाज किंवा नियंत्रणाचा अभाव यासारखी आवाजाची समस्या असल्यास ब्लूटूथ संपूर्ण आवाज वैशिष्ट्य बंद करते."</string> <string name="enable_terminal_title" msgid="95572094356054120">"स्थानिक टर्मिनल"</string> - <string name="enable_terminal_summary" msgid="67667852659359206">"स्थानिक शेल प्रवेश देणारा टर्मिनल अॅप सुरू करा"</string> + <string name="enable_terminal_summary" msgid="67667852659359206">"स्थानिक शेल प्रवेश देणारा टर्मिनल अॅप सुरू करा"</string> <string name="hdcp_checking_title" msgid="8605478913544273282">"HDCP तपासणी"</string> <string name="hdcp_checking_dialog_title" msgid="5141305530923283">"HDCP तपासणी वर्तन सेट करा"</string> <string name="debug_debugging_category" msgid="6781250159513471316">"डीबग करणे"</string> - <string name="debug_app" msgid="8349591734751384446">"डीबग अॅप निवडा"</string> + <string name="debug_app" msgid="8349591734751384446">"डीबग अॅप निवडा"</string> <string name="debug_app_not_set" msgid="718752499586403499">"कोणतेही डीबग अॅप्लिकेशन सेट नाही"</string> <string name="debug_app_set" msgid="2063077997870280017">"अॅप्लिकेशन डीबग करत आहे: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="select_application" msgid="5156029161289091703">"अॅप्लिकेशन निवडा"</string> @@ -315,7 +315,7 @@ <string name="immediately_destroy_activities_summary" msgid="3592221124808773368">"वापरकर्त्याने प्रत्येक अॅक्टिव्हिटी सोडताच ती नष्ट करा"</string> <string name="app_process_limit_title" msgid="4280600650253107163">"पार्श्वभूमी प्रक्रिया मर्यादा"</string> <string name="show_all_anrs" msgid="4924885492787069007">"बॅकग्राउंड ANR दाखवा"</string> - <string name="show_all_anrs_summary" msgid="6636514318275139826">"बॅकग्राउंड अॅप्ससाठी अॅप प्रतिसाद देत नाही दाखवते"</string> + <string name="show_all_anrs_summary" msgid="6636514318275139826">"बॅकग्राउंड अॅप्ससाठी अॅप प्रतिसाद देत नाही दाखवते"</string> <string name="show_notification_channel_warnings" msgid="1399948193466922683">"सूचना चॅनेल चेतावण्या दाखवा"</string> <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"एखादे अॅप वैध चॅनेलशिवाय सूचना पोस्ट करते तेव्हा स्क्रीनवर चेतावणी देते"</string> <string name="force_allow_on_external" msgid="3215759785081916381">"बाह्यवर अॅप्सना अनुमती देण्याची सक्ती करा"</string> @@ -343,7 +343,7 @@ <string name="inactive_apps_title" msgid="9042996804461901648">"स्टँडबाय अॅप्स"</string> <string name="inactive_app_inactive_summary" msgid="5091363706699855725">"निष्क्रिय. टॉगल करण्यासाठी टॅप करा."</string> <string name="inactive_app_active_summary" msgid="4174921824958516106">"सक्रिय. टॉगल करण्यासाठी टॅप करा."</string> - <string name="standby_bucket_summary" msgid="6567835350910684727">"अॅप स्टँडबाय स्थिती: <xliff:g id="BUCKET"> %s</xliff:g>"</string> + <string name="standby_bucket_summary" msgid="6567835350910684727">"अॅप स्टँडबाय स्थिती: <xliff:g id="BUCKET"> %s</xliff:g>"</string> <string name="runningservices_settings_title" msgid="8097287939865165213">"चालू सेवा"</string> <string name="runningservices_settings_summary" msgid="854608995821032748">"सध्या चालत असलेल्या सेवा पहा आणि नियंत्रित करा"</string> <string name="select_webview_provider_title" msgid="4628592979751918907">"वेबदृश्य अंमलबजावणी"</string> @@ -422,7 +422,7 @@ <string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"सिस्टम भाषा वापरा"</string> <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> साठी सेटिंग्ज उघडण्यात अयशस्वी"</string> <string name="ime_security_warning" msgid="4135828934735934248">"ही इनपुट पद्धत पासवर्ड आणि क्रेडिट कार्ड नंबर यासह, तुम्ही टाइप करता तो सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. ही <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> अॅपवरून येते. ही इनपुट पद्धत वापरायची?"</string> - <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"टीप: रीबूट केल्यानंतर, तुम्ही तुमचा फोन अनलॉक करे पर्यंत हे अॅप सुरू होऊ शकत नाही"</string> + <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"टीप: रीबूट केल्यानंतर, तुम्ही तुमचा फोन अनलॉक करे पर्यंत हे अॅप सुरू होऊ शकत नाही"</string> <string name="ims_reg_title" msgid="7609782759207241443">"IMS नोंदणी स्थिती"</string> <string name="ims_reg_status_registered" msgid="933003316932739188">"नोंदवलेले"</string> <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"नोंदवलेले नाही"</string> @@ -449,6 +449,6 @@ <string name="zen_mode_duration_always_prompt_title" msgid="6478923750878945501">"प्रत्येक वेळी विचारा"</string> <string name="zen_mode_forever" msgid="2704305038191592967">"तुम्ही बंद करेपर्यंत"</string> <string name="time_unit_just_now" msgid="6363336622778342422">"आत्ताच"</string> - <string name="updated_gfx_driver_dev_opt_in_app_summary" msgid="5309913444094165199">"अपडेट केलेले ग्राफिक ड्राइव्हर डेव्हलमेंटमध्ये वापरण्यासाठी अॅप निवडा"</string> + <string name="updated_gfx_driver_dev_opt_in_app_summary" msgid="5309913444094165199">"अपडेट केलेले ग्राफिक ड्राइव्हर डेव्हलमेंटमध्ये वापरण्यासाठी अॅप निवडा"</string> <string name="media_transfer_phone_device_name" msgid="1003823744105758574">"फोनचा स्पीकर"</string> </resources> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index ae9c5f2e21a4..e810ba248850 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -40,7 +40,7 @@ <string name="connected_via_passpoint" msgid="2826205693803088747">"Tilkoblet via %1$s"</string> <string name="available_via_passpoint" msgid="1617440946846329613">"Tilgjengelig via %1$s"</string> <string name="wifi_connected_no_internet" msgid="8202906332837777829">"Tilkoblet – ingen Internett-tilgang"</string> - <string name="wifi_status_no_internet" msgid="5784710974669608361">"Ingen Internett-tilkobling"</string> + <string name="wifi_status_no_internet" msgid="5784710974669608361">"Ingen internettilkobling"</string> <string name="wifi_status_sign_in_required" msgid="123517180404752756">"Pålogging kreves"</string> <string name="wifi_ap_unable_to_handle_new_sta" msgid="5348824313514404541">"Tilgangspunktet er midlertidig fullt"</string> <string name="connected_via_carrier" msgid="7583780074526041912">"Tilkoblet via %1$s"</string> @@ -75,7 +75,7 @@ <string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internett-tilgang"</string> <string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontaktdeling"</string> <string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Bruk til kontaktdeling"</string> - <string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Deling av Internett-tilkobling"</string> + <string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Deling av internettilkobling"</string> <string name="bluetooth_profile_map" msgid="1019763341565580450">"Tekstmeldinger"</string> <string name="bluetooth_profile_sap" msgid="5764222021851283125">"Tilgang til SIM-kortet"</string> <string name="bluetooth_profile_a2dp_high_quality" msgid="5444517801472820055">"HD-lyd: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> @@ -90,7 +90,7 @@ <string name="bluetooth_opp_profile_summary_not_connected" msgid="1267091356089086285">"Ikke koblet til tjener for filoverføring"</string> <string name="bluetooth_hid_profile_summary_connected" msgid="3381760054215168689">"Koblet til inndataenhet"</string> <string name="bluetooth_pan_user_profile_summary_connected" msgid="6436258151814414028">"Koblet til enhet for Internett-tilgang"</string> - <string name="bluetooth_pan_nap_profile_summary_connected" msgid="1322694224800769308">"Deler lokal Internett-tilkobling med enhet"</string> + <string name="bluetooth_pan_nap_profile_summary_connected" msgid="1322694224800769308">"Deler lokal internettilkobling med enhet"</string> <string name="bluetooth_pan_profile_summary_use_for" msgid="5736111170225304239">"Bruk for Internett-tilgang"</string> <string name="bluetooth_map_profile_summary_use_for" msgid="5154200119919927434">"Bruk for kart"</string> <string name="bluetooth_sap_profile_summary_use_for" msgid="7085362712786907993">"Bruk for tilgang til SIM-kortet"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java index 1457fcfadc89..74aaf3c26aba 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java @@ -38,6 +38,7 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.text.style.ImageSpan; +import android.util.Log; import android.view.MenuItem; import android.widget.TextView; @@ -52,6 +53,9 @@ import java.util.List; * support message dialog. */ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { + + private static final String LOG_TAG = "RestrictedLockUtils"; + /** * @return drawables for displaying with settings that are locked by a device admin. */ @@ -305,6 +309,42 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { return null; } + /** + * @param userId user id of a managed profile. + * @return profile owner admin if cross profile calendar is disallowed. + */ + public static EnforcedAdmin getCrossProfileCalendarEnforcingAdmin(Context context, int userId) { + final Context managedProfileContext = createPackageContextAsUser( + context, userId); + final DevicePolicyManager dpm = managedProfileContext.getSystemService( + DevicePolicyManager.class); + if (dpm == null) { + return null; + } + final EnforcedAdmin admin = getProfileOwner(context, userId); + if (admin == null) { + return null; + } + if (dpm.getCrossProfileCalendarPackages().isEmpty()) { + return admin; + } + return null; + } + + /** + * @param userId user id of a managed profile. + * @return a context created from the given context for the given user, or null if it fails. + */ + private static Context createPackageContextAsUser(Context context, int userId) { + try { + return context.createPackageContextAsUser( + context.getPackageName(), 0 /* flags */, UserHandle.of(userId)); + } catch (PackageManager.NameNotFoundException e) { + Log.e(LOG_TAG, "Failed to create user context", e); + } + return null; + } + public static EnforcedAdmin checkIfAccessibilityServiceDisallowed(Context context, String packageName, int userId) { DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java b/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java index ec8e9561ea83..2387b01d341d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java @@ -15,10 +15,9 @@ */ package com.android.settingslib.applications; -import android.annotation.NonNull; import android.content.Context; -import android.content.pm.permission.RuntimePermissionPresentationInfo; -import android.content.pm.permission.RuntimePermissionPresenter; +import android.permission.RuntimePermissionPresentationInfo; +import android.permission.RuntimePermissionPresenter; import java.text.Collator; import java.util.ArrayList; @@ -31,37 +30,33 @@ public class PermissionsSummaryHelper { final PermissionsResultCallback callback) { final RuntimePermissionPresenter presenter = RuntimePermissionPresenter.getInstance(context); - presenter.getAppPermissions(pkg, new RuntimePermissionPresenter.OnResultCallback() { - @Override - public void onGetAppPermissions( - @NonNull List<RuntimePermissionPresentationInfo> permissions) { - final int permissionCount = permissions.size(); + presenter.getAppPermissions(pkg, permissions -> { + final int permissionCount = permissions.size(); - int grantedStandardCount = 0; - int grantedAdditionalCount = 0; - int requestedCount = 0; - List<CharSequence> grantedStandardLabels = new ArrayList<>(); + int grantedStandardCount = 0; + int grantedAdditionalCount = 0; + int requestedCount = 0; + List<CharSequence> grantedStandardLabels = new ArrayList<>(); - for (int i = 0; i < permissionCount; i++) { - RuntimePermissionPresentationInfo permission = permissions.get(i); - requestedCount++; - if (permission.isGranted()) { - if (permission.isStandard()) { - grantedStandardLabels.add(permission.getLabel()); - grantedStandardCount++; - } else { - grantedAdditionalCount++; - } + for (int i = 0; i < permissionCount; i++) { + RuntimePermissionPresentationInfo permission = permissions.get(i); + requestedCount++; + if (permission.isGranted()) { + if (permission.isStandard()) { + grantedStandardLabels.add(permission.getLabel()); + grantedStandardCount++; + } else { + grantedAdditionalCount++; } } + } - Collator collator = Collator.getInstance(); - collator.setStrength(Collator.PRIMARY); - Collections.sort(grantedStandardLabels, collator); + Collator collator = Collator.getInstance(); + collator.setStrength(Collator.PRIMARY); + Collections.sort(grantedStandardLabels, collator); - callback.onPermissionSummaryResult(grantedStandardCount, requestedCount, - grantedAdditionalCount, grantedStandardLabels); - } + callback.onPermissionSummaryResult(grantedStandardCount, requestedCount, + grantedAdditionalCount, grantedStandardLabels); }, null); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 9270d13002d7..1bffff753513 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -71,15 +71,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> boolean mJustDiscovered; - private int mMessageRejectionCount; - private final Collection<Callback> mCallbacks = new ArrayList<>(); - // How many times user should reject the connection to make the choice persist. - private final static int MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST = 2; - - private final static String MESSAGE_REJECTION_COUNT_PREFS_NAME = "bluetooth_message_reject"; - /** * Last time a bt profile auto-connect was attempted. * If an ACTION_UUID intent comes in within @@ -348,7 +341,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> fetchActiveDevices(); migratePhonebookPermissionChoice(); migrateMessagePermissionChoice(); - fetchMessageRejectionCount(); dispatchAttributesChanged(); } @@ -642,8 +634,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_UNKNOWN); mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_UNKNOWN); mDevice.setSimAccessPermission(BluetoothDevice.ACCESS_UNKNOWN); - mMessageRejectionCount = 0; - saveMessageRejectionCount(); } refresh(); @@ -797,34 +787,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> editor.commit(); } - /** - * @return Whether this rejection should persist. - */ - public boolean checkAndIncreaseMessageRejectionCount() { - if (mMessageRejectionCount < MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST) { - mMessageRejectionCount++; - saveMessageRejectionCount(); - } - return mMessageRejectionCount >= MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST; - } - - private void fetchMessageRejectionCount() { - SharedPreferences preference = mContext.getSharedPreferences( - MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE); - mMessageRejectionCount = preference.getInt(mDevice.getAddress(), 0); - } - - private void saveMessageRejectionCount() { - SharedPreferences.Editor editor = mContext.getSharedPreferences( - MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE).edit(); - if (mMessageRejectionCount == 0) { - editor.remove(mDevice.getAddress()); - } else { - editor.putInt(mDevice.getAddress(), mMessageRejectionCount); - } - editor.commit(); - } - private void processPhonebookAccess() { if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) return; diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java index 67cfe6bfd18a..91892abdfb44 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java @@ -363,7 +363,8 @@ public class TileUtils { return null; } try { - return provider.call(context.getPackageName(), method, uriString, null); + return provider.call(context.getPackageName(), uri.getAuthority(), + method, uriString, null); } catch (RemoteException e) { return null; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java new file mode 100644 index 000000000000..c3bc8da89685 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.widget; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class AppEntitiesHeaderControllerTest { + + private static final CharSequence TITLE = "APP_TITLE"; + private static final CharSequence SUMMARY = "APP_SUMMARY"; + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private Context mContext; + private Drawable mIcon; + private View mAppEntitiesHeaderView; + private AppEntitiesHeaderController mController; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mAppEntitiesHeaderView = LayoutInflater.from(mContext).inflate( + R.layout.app_entities_header, null /* root */); + mIcon = mContext.getDrawable(R.drawable.ic_menu); + mController = AppEntitiesHeaderController.newInstance(mContext, + mAppEntitiesHeaderView); + } + + @Test + public void assert_amountOfMaximumAppsAreThree() { + assertThat(AppEntitiesHeaderController.MAXIMUM_APPS).isEqualTo(3); + } + + @Test + public void setHeaderTitleRes_setTextRes_shouldSetToTitleView() { + mController.setHeaderTitleRes(R.string.expand_button_title).apply(); + final TextView view = mAppEntitiesHeaderView.findViewById(R.id.header_title); + + assertThat(view.getText()).isEqualTo(mContext.getText(R.string.expand_button_title)); + } + + @Test + public void setHeaderDetailsRes_setTextRes_shouldSetToDetailsView() { + mController.setHeaderDetailsRes(R.string.expand_button_title).apply(); + final TextView view = mAppEntitiesHeaderView.findViewById(R.id.header_details); + + assertThat(view.getText()).isEqualTo(mContext.getText(R.string.expand_button_title)); + } + + @Test + public void setHeaderDetailsClickListener_setClickListener_detailsViewAttachClickListener() { + mController.setHeaderDetailsClickListener(v -> { + }).apply(); + final TextView view = mAppEntitiesHeaderView.findViewById(R.id.header_details); + + assertThat(view.hasOnClickListeners()).isTrue(); + } + + @Test + public void setAppEntity_indexLessThanZero_shouldThrowArrayIndexOutOfBoundsException() { + thrown.expect(ArrayIndexOutOfBoundsException.class); + + mController.setAppEntity(-1, mIcon, TITLE, SUMMARY); + } + + @Test + public void asetAppEntity_indexGreaterThanMaximum_shouldThrowArrayIndexOutOfBoundsException() { + thrown.expect(ArrayIndexOutOfBoundsException.class); + + mController.setAppEntity(AppEntitiesHeaderController.MAXIMUM_APPS + 1, mIcon, TITLE, + SUMMARY); + } + + @Test + public void setAppEntity_addAppToIndex0_shouldShowAppView1() { + mController.setAppEntity(0, mIcon, TITLE, SUMMARY).apply(); + final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view); + final ImageView appIconView = app1View.findViewById(R.id.app_icon); + final TextView appTitle = app1View.findViewById(R.id.app_title); + final TextView appSummary = app1View.findViewById(R.id.app_summary); + + assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(appIconView.getDrawable()).isNotNull(); + assertThat(appTitle.getText()).isEqualTo(TITLE); + assertThat(appSummary.getText()).isEqualTo(SUMMARY); + } + + @Test + public void setAppEntity_addAppToIndex1_shouldShowAppView2() { + mController.setAppEntity(1, mIcon, TITLE, SUMMARY).apply(); + final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view); + final ImageView appIconView = app2View.findViewById(R.id.app_icon); + final TextView appTitle = app2View.findViewById(R.id.app_title); + final TextView appSummary = app2View.findViewById(R.id.app_summary); + + assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(appIconView.getDrawable()).isNotNull(); + assertThat(appTitle.getText()).isEqualTo(TITLE); + assertThat(appSummary.getText()).isEqualTo(SUMMARY); + } + + @Test + public void setAppEntity_addAppToIndex2_shouldShowAppView3() { + mController.setAppEntity(2, mIcon, TITLE, SUMMARY).apply(); + final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view); + final ImageView appIconView = app3View.findViewById(R.id.app_icon); + final TextView appTitle = app3View.findViewById(R.id.app_title); + final TextView appSummary = app3View.findViewById(R.id.app_summary); + + assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(appIconView.getDrawable()).isNotNull(); + assertThat(appTitle.getText()).isEqualTo(TITLE); + assertThat(appSummary.getText()).isEqualTo(SUMMARY); + } + + @Test + public void removeAppEntity_removeIndex0_shouldNotShowAppView1() { + mController.setAppEntity(0, mIcon, TITLE, SUMMARY) + .setAppEntity(1, mIcon, TITLE, SUMMARY).apply(); + final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view); + final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view); + + assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE); + + mController.removeAppEntity(0).apply(); + + assertThat(app1View.getVisibility()).isEqualTo(View.GONE); + assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void clearAllAppEntities_shouldNotShowAllAppViews() { + mController.setAppEntity(0, mIcon, TITLE, SUMMARY) + .setAppEntity(1, mIcon, TITLE, SUMMARY) + .setAppEntity(2, mIcon, TITLE, SUMMARY).apply(); + final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view); + final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view); + final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view); + + assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE); + + mController.clearAllAppEntities().apply(); + assertThat(app1View.getVisibility()).isEqualTo(View.GONE); + assertThat(app2View.getVisibility()).isEqualTo(View.GONE); + assertThat(app3View.getVisibility()).isEqualTo(View.GONE); + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java new file mode 100644 index 000000000000..352091804de2 --- /dev/null +++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2018 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.providers.settings; + +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.ActivityManager; +import android.content.IContentProvider; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Process; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.ShellCommand; +import android.provider.Settings; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Receives shell commands from the command line related to device config flags, and dispatches them + * to the SettingsProvider. + * + * @hide + */ +@SystemApi +public final class DeviceConfigService extends Binder { + /** + * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System + * API. + */ + private static final Uri CONFIG_CONTENT_URI = + Uri.parse("content://" + Settings.AUTHORITY + "/config"); + + final SettingsProvider mProvider; + + public DeviceConfigService(SettingsProvider provider) { + mProvider = provider; + } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + (new MyShellCommand(mProvider)).exec(this, in, out, err, args, callback, resultReceiver); + } + + static final class MyShellCommand extends ShellCommand { + final SettingsProvider mProvider; + + enum CommandVerb { + UNSPECIFIED, + GET, + PUT, + DELETE, + LIST, + RESET, + } + + MyShellCommand(SettingsProvider provider) { + mProvider = provider; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) { + onHelp(); + return -1; + } + + final PrintWriter perr = getErrPrintWriter(); + boolean isValid = false; + CommandVerb verb; + if ("get".equalsIgnoreCase(cmd)) { + verb = CommandVerb.GET; + } else if ("put".equalsIgnoreCase(cmd)) { + verb = CommandVerb.PUT; + } else if ("delete".equalsIgnoreCase(cmd)) { + verb = CommandVerb.DELETE; + } else if ("list".equalsIgnoreCase(cmd)) { + verb = CommandVerb.LIST; + if (peekNextArg() == null) { + isValid = true; + } + } else if ("reset".equalsIgnoreCase(cmd)) { + verb = CommandVerb.RESET; + } else { + // invalid + perr.println("Invalid command: " + cmd); + return -1; + } + + int resetMode = -1; + boolean makeDefault = false; + String namespace = null; + String key = null; + String value = null; + String arg = null; + while ((arg = getNextArg()) != null) { + if (verb == CommandVerb.RESET) { + if (resetMode == -1) { + if ("untrusted_defaults".equalsIgnoreCase(arg)) { + resetMode = Settings.RESET_MODE_UNTRUSTED_DEFAULTS; + } else if ("untrusted_clear".equalsIgnoreCase(arg)) { + resetMode = Settings.RESET_MODE_UNTRUSTED_CHANGES; + } else if ("trusted_defaults".equalsIgnoreCase(arg)) { + resetMode = Settings.RESET_MODE_TRUSTED_DEFAULTS; + } else { + // invalid + perr.println("Invalid reset mode: " + arg); + return -1; + } + if (peekNextArg() == null) { + isValid = true; + } + } else { + namespace = arg; + if (peekNextArg() == null) { + isValid = true; + } else { + // invalid + perr.println("Too many arguments"); + return -1; + } + } + } else if (namespace == null) { + namespace = arg; + if (verb == CommandVerb.LIST) { + if (peekNextArg() == null) { + isValid = true; + } else { + // invalid + perr.println("Too many arguments"); + return -1; + } + } + } else if (key == null) { + key = arg; + if ((verb == CommandVerb.GET || verb == CommandVerb.DELETE)) { + if (peekNextArg() == null) { + isValid = true; + } else { + // invalid + perr.println("Too many arguments"); + return -1; + } + } + } else if (value == null) { + value = arg; + if (verb == CommandVerb.PUT && peekNextArg() == null) { + isValid = true; + } + } else if ("default".equalsIgnoreCase(arg)) { + makeDefault = true; + if (verb == CommandVerb.PUT && peekNextArg() == null) { + isValid = true; + } else { + // invalid + perr.println("Too many arguments"); + return -1; + } + } + } + + if (!isValid) { + perr.println("Bad arguments"); + return -1; + } + + final IContentProvider iprovider = mProvider.getIContentProvider(); + final PrintWriter pout = getOutPrintWriter(); + switch (verb) { + case GET: + pout.println(get(iprovider, namespace, key)); + break; + case PUT: + put(iprovider, namespace, key, value, makeDefault); + break; + case DELETE: + pout.println(delete(iprovider, namespace, key) + ? "Successfully deleted " + key + " from " + namespace + : "Failed to delete " + key + " from " + namespace); + break; + case LIST: + for (String line : list(iprovider, namespace)) { + pout.println(line); + } + break; + case RESET: + reset(iprovider, resetMode, namespace); + break; + default: + perr.println("Unspecified command"); + return -1; + } + return 0; + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("Device Config (device_config) commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" get NAMESPACE KEY"); + pw.println(" Retrieve the current value of KEY from the given NAMESPACE."); + pw.println(" put NAMESPACE KEY VALUE [default]"); + pw.println(" Change the contents of KEY to VALUE for the given NAMESPACE."); + pw.println(" {default} to set as the default value."); + pw.println(" delete NAMESPACE KEY"); + pw.println(" Delete the entry for KEY for the given NAMESPACE."); + pw.println(" list [NAMESPACE]"); + pw.println(" Print all keys and values defined, optionally for the given " + + "NAMESPACE."); + pw.println(" reset RESET_MODE [NAMESPACE]"); + pw.println(" Reset all flag values, optionally for a NAMESPACE, according to " + + "RESET_MODE."); + pw.println(" RESET_MODE is one of {untrusted_defaults, untrusted_clear, " + + "trusted_defaults}"); + pw.println(" NAMESPACE limits which flags are reset if provided, otherwise all " + + "flags are reset"); + } + + private String get(IContentProvider provider, String namespace, String key) { + String compositeKey = namespace + "/" + key; + String result = null; + try { + Bundle args = new Bundle(); + args.putInt(Settings.CALL_METHOD_USER_KEY, + ActivityManager.getService().getCurrentUser().id); + Bundle b = provider.call(resolveCallingPackage(), Settings.CALL_METHOD_GET_CONFIG, + compositeKey, args); + if (b != null) { + result = b.getPairValue(); + } + } catch (RemoteException e) { + throw new RuntimeException("Failed in IPC", e); + } + return result; + } + + private void put(IContentProvider provider, String namespace, String key, String value, + boolean makeDefault) { + String compositeKey = namespace + "/" + key; + + try { + Bundle args = new Bundle(); + args.putString(Settings.NameValueTable.VALUE, value); + args.putInt(Settings.CALL_METHOD_USER_KEY, + ActivityManager.getService().getCurrentUser().id); + if (makeDefault) { + args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true); + } + provider.call(resolveCallingPackage(), Settings.CALL_METHOD_PUT_CONFIG, + compositeKey, args); + } catch (RemoteException e) { + throw new RuntimeException("Failed in IPC", e); + } + } + + private boolean delete(IContentProvider provider, String namespace, String key) { + String compositeKey = namespace + "/" + key; + boolean success; + + try { + Bundle args = new Bundle(); + args.putInt(Settings.CALL_METHOD_USER_KEY, + ActivityManager.getService().getCurrentUser().id); + Bundle b = provider.call(resolveCallingPackage(), + Settings.CALL_METHOD_DELETE_CONFIG, compositeKey, args); + success = (b != null && b.getInt(SettingsProvider.RESULT_ROWS_DELETED) == 1); + } catch (RemoteException e) { + throw new RuntimeException("Failed in IPC", e); + } + return success; + } + + private List<String> list(IContentProvider provider, @Nullable String namespace) { + final ArrayList<String> lines = new ArrayList<>(); + + try { + Bundle args = new Bundle(); + args.putInt(Settings.CALL_METHOD_USER_KEY, + ActivityManager.getService().getCurrentUser().id); + if (namespace != null) { + args.putString(Settings.CALL_METHOD_PREFIX_KEY, namespace); + } + Bundle b = provider.call(resolveCallingPackage(), + Settings.CALL_METHOD_LIST_CONFIG, null, args); + if (b != null) { + Map<String, String> flagsToValues = + (HashMap) b.getSerializable(Settings.NameValueTable.VALUE); + for (String key : flagsToValues.keySet()) { + lines.add(key + "=" + flagsToValues.get(key)); + } + } + + Collections.sort(lines); + } catch (RemoteException e) { + throw new RuntimeException("Failed in IPC", e); + } + return lines; + } + + private void reset(IContentProvider provider, int resetMode, @Nullable String namespace) { + try { + Bundle args = new Bundle(); + args.putInt(Settings.CALL_METHOD_USER_KEY, + ActivityManager.getService().getCurrentUser().id); + args.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, resetMode); + args.putString(Settings.CALL_METHOD_PREFIX_KEY, namespace); + provider.call( + resolveCallingPackage(), Settings.CALL_METHOD_RESET_CONFIG, null, args); + } catch (RemoteException e) { + throw new RuntimeException("Failed in IPC", e); + } + } + + private static String resolveCallingPackage() { + switch (Binder.getCallingUid()) { + case Process.ROOT_UID: { + return "root"; + } + + case Process.SHELL_UID: { + return "com.android.shell"; + } + + default: { + return null; + } + } + } + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index df5b1467f9d9..e3d3d81704a8 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -208,9 +208,14 @@ class SettingsProtoDumpUtil { GlobalSettingsProto.Autofill.MAX_VISIBLE_DATASETS); p.end(autofillToken); + final long backupToken = p.start(GlobalSettingsProto.BACKUP); dumpSetting(s, p, Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS, - GlobalSettingsProto.BACKUP_AGENT_TIMEOUT_PARAMETERS); + GlobalSettingsProto.Backup.BACKUP_AGENT_TIMEOUT_PARAMETERS); + dumpSetting(s, p, + Settings.Global.BACKUP_MULTI_USER_ENABLED, + GlobalSettingsProto.Backup.BACKUP_MULTI_USER_ENABLED); + p.end(backupToken); final long batteryToken = p.start(GlobalSettingsProto.BATTERY); dumpSetting(s, p, @@ -418,6 +423,12 @@ class SettingsProtoDumpUtil { Settings.Global.CONTACTS_DATABASE_WAL_ENABLED, GlobalSettingsProto.CONTACTS_DATABASE_WAL_ENABLED); + final long contentCaptureToken = p.start(GlobalSettingsProto.CONTENT_CAPTURE); + dumpSetting(s, p, + Settings.Global.CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED, + GlobalSettingsProto.ContentCapture.SERVICE_EXPLICITLY_ENABLED); + p.end(contentCaptureToken); + final long dataToken = p.start(GlobalSettingsProto.DATA); // Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA intentionally excluded. dumpSetting(s, p, @@ -679,8 +690,14 @@ class SettingsProtoDumpUtil { Settings.Global.GPU_DEBUG_LAYERS, GlobalSettingsProto.Gpu.DEBUG_LAYERS); dumpSetting(s, p, - Settings.Global.ANGLE_ENABLED_APP, - GlobalSettingsProto.Gpu.ANGLE_ENABLED_APP); + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE, + GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_ALL_ANGLE); + dumpSetting(s, p, + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, + GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_PKGS); + dumpSetting(s, p, + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, + GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_VALUES); dumpSetting(s, p, Settings.Global.GPU_DEBUG_LAYER_APP, GlobalSettingsProto.Gpu.DEBUG_LAYER_APP); @@ -998,6 +1015,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, GlobalSettingsProto.Notification.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS); + dumpSetting(s, p, + Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, + GlobalSettingsProto.Notification.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS); p.end(notificationToken); dumpSetting(s, p, @@ -1165,12 +1185,6 @@ class SettingsProtoDumpUtil { GlobalSettingsProto.SmartSelection.UPDATE_METADATA_URL); p.end(smartSelectToken); - final long smartSuggestionsToken = p.start(GlobalSettingsProto.SMART_SUGGESTIONS); - dumpSetting(s, p, - Settings.Global.SMART_SUGGESTIONS_SERVICE_EXPLICITLY_ENABLED, - GlobalSettingsProto.SmartSuggestions.SERVICE_EXPLICITLY_ENABLED); - p.end(smartSuggestionsToken); - final long smsToken = p.start(GlobalSettingsProto.SMS); dumpSetting(s, p, Settings.Global.SMS_OUTGOING_CHECK_INTERVAL_MS, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 00ea45c4e024..b071355986f5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -94,6 +94,7 @@ import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -147,6 +148,7 @@ public class SettingsProvider extends ContentProvider { private static final String TABLE_SYSTEM = "system"; private static final String TABLE_SECURE = "secure"; private static final String TABLE_GLOBAL = "global"; + private static final String TABLE_CONFIG = "config"; // Old tables no longer exist. private static final String TABLE_FAVORITES = "favorites"; @@ -333,6 +335,7 @@ public class SettingsProvider extends ContentProvider { startWatchingUserRestrictionChanges(); }); ServiceManager.addService("settings", new SettingsService(this)); + ServiceManager.addService("device_config", new DeviceConfigService(this)); return true; } @@ -414,9 +417,8 @@ public class SettingsProvider extends ContentProvider { case Settings.CALL_METHOD_PUT_CONFIG: { String value = getSettingValue(args); - String tag = getSettingTag(args); final boolean makeDefault = getSettingMakeDefault(args); - insertConfigSetting(name, value, tag, makeDefault, requestingUserId, false); + insertConfigSetting(name, value, makeDefault); break; } @@ -444,8 +446,8 @@ public class SettingsProvider extends ContentProvider { case Settings.CALL_METHOD_RESET_CONFIG: { final int mode = getResetModeEnforcingPermission(args); - String tag = getSettingTag(args); - resetConfigSetting(requestingUserId, mode, tag); + String prefix = getSettingPrefix(args); + resetConfigSetting(mode, prefix); break; } @@ -463,8 +465,15 @@ public class SettingsProvider extends ContentProvider { break; } - case Settings.CALL_METHOD_DELETE_SYSTEM: { - int rows = deleteSystemSetting(name, requestingUserId) ? 1 : 0; + case Settings.CALL_METHOD_DELETE_CONFIG: { + int rows = deleteConfigSetting(name) ? 1 : 0; + Bundle result = new Bundle(); + result.putInt(RESULT_ROWS_DELETED, rows); + return result; + } + + case Settings.CALL_METHOD_DELETE_GLOBAL: { + int rows = deleteGlobalSetting(name, requestingUserId, false) ? 1 : 0; Bundle result = new Bundle(); result.putInt(RESULT_ROWS_DELETED, rows); return result; @@ -477,17 +486,25 @@ public class SettingsProvider extends ContentProvider { return result; } - case Settings.CALL_METHOD_DELETE_GLOBAL: { - int rows = deleteGlobalSetting(name, requestingUserId, false) ? 1 : 0; + case Settings.CALL_METHOD_DELETE_SYSTEM: { + int rows = deleteSystemSetting(name, requestingUserId) ? 1 : 0; Bundle result = new Bundle(); result.putInt(RESULT_ROWS_DELETED, rows); return result; } - case Settings.CALL_METHOD_LIST_SYSTEM: { + case Settings.CALL_METHOD_LIST_CONFIG: { + String prefix = getSettingPrefix(args); + Bundle result = new Bundle(); + result.putSerializable( + Settings.NameValueTable.VALUE, (HashMap) getAllConfigFlags(prefix)); + return result; + } + + case Settings.CALL_METHOD_LIST_GLOBAL: { Bundle result = new Bundle(); result.putStringArrayList(RESULT_SETTINGS_LIST, - buildSettingsList(getAllSystemSettings(requestingUserId, null))); + buildSettingsList(getAllGlobalSettings(null))); return result; } @@ -498,10 +515,10 @@ public class SettingsProvider extends ContentProvider { return result; } - case Settings.CALL_METHOD_LIST_GLOBAL: { + case Settings.CALL_METHOD_LIST_SYSTEM: { Bundle result = new Bundle(); result.putStringArrayList(RESULT_SETTINGS_LIST, - buildSettingsList(getAllGlobalSettings(null))); + buildSettingsList(getAllSystemSettings(requestingUserId, null))); return result; } @@ -1050,47 +1067,53 @@ public class SettingsProvider extends ContentProvider { } } - private boolean insertConfigSetting(String name, String value, String tag, - boolean makeDefault, int requestingUserId, boolean forceNotify) { + private boolean insertConfigSetting(String name, String value, boolean makeDefault) { if (DEBUG) { Slog.v(LOG_TAG, "insertConfigSetting(" + name + ", " + value + ", " - + ", " + tag + ", " + makeDefault + ", " + requestingUserId - + ", " + forceNotify + ")"); + + makeDefault + ")"); } - return mutateConfigSetting(name, value, tag, makeDefault, requestingUserId, - MUTATION_OPERATION_INSERT, forceNotify, 0); + return mutateConfigSetting(name, value, null, makeDefault, + MUTATION_OPERATION_INSERT, 0); } - private void resetConfigSetting(int requestingUserId, int mode, String tag) { + private boolean deleteConfigSetting(String name) { if (DEBUG) { - Slog.v(LOG_TAG, "resetConfigSetting(" + requestingUserId + ", " - + mode + ", " + tag + ")"); + Slog.v(LOG_TAG, "deleteConfigSetting(" + name + ")"); } - mutateConfigSetting(null, null, tag, false, requestingUserId, - MUTATION_OPERATION_RESET, false, mode); + return mutateConfigSetting(name, null, null, false, + MUTATION_OPERATION_DELETE, 0); } - private boolean mutateConfigSetting(String name, String value, String tag, - boolean makeDefault, int requestingUserId, int operation, boolean forceNotify, - int mode) { + private void resetConfigSetting(int mode, String prefix) { + if (DEBUG) { + Slog.v(LOG_TAG, "resetConfigSetting(" + mode + ", " + prefix + ")"); + } + mutateConfigSetting(null, null, prefix, false, + MUTATION_OPERATION_RESET, mode); + } + + private boolean mutateConfigSetting(String name, String value, String prefix, + boolean makeDefault, int operation, int mode) { // TODO(b/117663715): check the new permission when it's added. // enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS); - // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId); - // Perform the mutation. synchronized (mLock) { switch (operation) { case MUTATION_OPERATION_INSERT: { return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG, - UserHandle.USER_SYSTEM, name, value, tag, makeDefault, - getCallingPackage(), forceNotify, null); + UserHandle.USER_SYSTEM, name, value, null, makeDefault, true, + getCallingPackage(), false, null); + } + + case MUTATION_OPERATION_DELETE: { + return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_CONFIG, + UserHandle.USER_SYSTEM, name, false, null); } case MUTATION_OPERATION_RESET: { mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG, - UserHandle.USER_SYSTEM, getCallingPackage(), mode, tag); + UserHandle.USER_SYSTEM, getCallingPackage(), mode, null, prefix); } return true; } } @@ -1098,6 +1121,34 @@ public class SettingsProvider extends ContentProvider { return false; } + private Map<String, String> getAllConfigFlags(@Nullable String prefix) { + if (DEBUG) { + Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix); + } + + synchronized (mLock) { + // Get the settings. + SettingsState settingsState = mSettingsRegistry.getSettingsLocked( + SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM); + + List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_CONFIG, + UserHandle.USER_SYSTEM); + + final int nameCount = names.size(); + Map<String, String> flagsToValues = new HashMap<>(names.size()); + + for (int i = 0; i < nameCount; i++) { + String name = names.get(i); + Setting setting = settingsState.getSettingLocked(name); + if (prefix == null || setting.getName().startsWith(prefix)) { + flagsToValues.put(setting.getName(), setting.getValue()); + } + } + + return flagsToValues; + } + } + private Cursor getAllGlobalSettings(String[] projection) { if (DEBUG) { Slog.v(LOG_TAG, "getAllGlobalSettings()"); @@ -2085,6 +2136,13 @@ public class SettingsProvider extends ContentProvider { return (args != null) ? args.getString(Settings.CALL_METHOD_TAG_KEY) : null; } + private static String getSettingPrefix(Bundle args) { + String prefix = (args != null) ? args.getString(Settings.CALL_METHOD_PREFIX_KEY) : null; + // Append '/' to ensure we only match properties with this exact prefix. + // i.e. "foo" should match "foo/property" but not "foobar/property" + return prefix != null ? prefix + "/" : null; + } + private static boolean getSettingMakeDefault(Bundle args) { return (args != null) && args.getBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY); } @@ -2568,13 +2626,20 @@ public class SettingsProvider extends ContentProvider { public boolean insertSettingLocked(int type, int userId, String name, String value, String tag, boolean makeDefault, String packageName, boolean forceNotify, Set<String> criticalSettings) { + return insertSettingLocked(type, userId, name, value, tag, makeDefault, false, + packageName, forceNotify, criticalSettings); + } + + public boolean insertSettingLocked(int type, int userId, String name, String value, + String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName, + boolean forceNotify, Set<String> criticalSettings) { final int key = makeKey(type, userId); boolean success = false; SettingsState settingsState = peekSettingsStateLocked(key); if (settingsState != null) { success = settingsState.insertSettingLocked(name, value, - tag, makeDefault, packageName); + tag, makeDefault, forceNonSystemPackage, packageName); } if (success && criticalSettings != null && criticalSettings.contains(name)) { @@ -2644,6 +2709,11 @@ public class SettingsProvider extends ContentProvider { public void resetSettingsLocked(int type, int userId, String packageName, int mode, String tag) { + resetSettingsLocked(type, userId, packageName, mode, tag, null); + } + + public void resetSettingsLocked(int type, int userId, String packageName, int mode, + String tag, @Nullable String prefix) { final int key = makeKey(type, userId); SettingsState settingsState = peekSettingsStateLocked(key); if (settingsState == null) { @@ -2656,7 +2726,8 @@ public class SettingsProvider extends ContentProvider { boolean someSettingChanged = false; Setting setting = settingsState.getSettingLocked(name); if (packageName.equals(setting.getPackageName())) { - if (tag != null && !tag.equals(setting.getTag())) { + if ((tag != null && !tag.equals(setting.getTag())) + || (prefix != null && !setting.getName().startsWith(prefix))) { continue; } if (settingsState.resetSettingLocked(name)) { @@ -2676,6 +2747,9 @@ public class SettingsProvider extends ContentProvider { Setting setting = settingsState.getSettingLocked(name); if (!SettingsState.isSystemPackage(getContext(), setting.getPackageName())) { + if (prefix != null && !setting.getName().startsWith(prefix)) { + continue; + } if (settingsState.resetSettingLocked(name)) { someSettingChanged = true; notifyForSettingsChange(key, name); @@ -2693,6 +2767,9 @@ public class SettingsProvider extends ContentProvider { Setting setting = settingsState.getSettingLocked(name); if (!SettingsState.isSystemPackage(getContext(), setting.getPackageName())) { + if (prefix != null && !setting.getName().startsWith(prefix)) { + continue; + } if (setting.isDefaultFromSystem()) { if (settingsState.resetSettingLocked(name)) { someSettingChanged = true; @@ -2713,6 +2790,9 @@ public class SettingsProvider extends ContentProvider { for (String name : settingsState.getSettingNamesLocked()) { Setting setting = settingsState.getSettingLocked(name); boolean someSettingChanged = false; + if (prefix != null && !setting.getName().startsWith(prefix)) { + continue; + } if (setting.isDefaultFromSystem()) { if (settingsState.resetSettingLocked(name)) { someSettingChanged = true; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java index 13537c466eed..36360a31a4b7 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java @@ -105,7 +105,7 @@ final public class SettingsService extends Binder { RESET, } - int mUser = -1; // unspecified + int mUser = UserHandle.USER_NULL; CommandVerb mVerb = CommandVerb.UNSPECIFIED; String mTable = null; String mKey = null; @@ -132,15 +132,15 @@ final public class SettingsService extends Binder { String arg = cmd; do { if ("--user".equals(arg)) { - if (mUser != -1) { - // --user specified more than once; invalid + if (mUser != UserHandle.USER_NULL) { + perr.println("Invalid user: --user specified more than once"); break; } - arg = getNextArgRequired(); - if ("current".equals(arg) || "cur".equals(arg)) { - mUser = UserHandle.USER_CURRENT; - } else { - mUser = Integer.parseInt(arg); + mUser = UserHandle.parseUserArg(getNextArgRequired()); + + if (mUser == UserHandle.USER_ALL) { + perr.println("Invalid user: all"); + return -1; } } else if (mVerb == CommandVerb.UNSPECIFIED) { if ("get".equalsIgnoreCase(arg)) { @@ -254,16 +254,13 @@ final public class SettingsService extends Binder { return -1; } - if (mUser == UserHandle.USER_CURRENT) { + if (mUser == UserHandle.USER_NULL || mUser == UserHandle.USER_CURRENT) { try { mUser = ActivityManager.getService().getCurrentUser().id; } catch (RemoteException e) { throw new RuntimeException("Failed in IPC", e); } } - if (mUser < 0) { - mUser = UserHandle.USER_SYSTEM; - } UserManager userManager = UserManager.get(mProvider.getContext()); if (userManager.getUserInfo(mUser) == null) { perr.println("Invalid user: " + mUser); @@ -312,8 +309,8 @@ final public class SettingsService extends Binder { try { Bundle arg = new Bundle(); arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); - Bundle result = - provider.call(resolveCallingPackage(), callListCommand, null, arg); + Bundle result = provider.call(resolveCallingPackage(), Settings.AUTHORITY, + callListCommand, null, arg); lines.addAll(result.getStringArrayList(SettingsProvider.RESULT_SETTINGS_LIST)); Collections.sort(lines); } catch (RemoteException e) { @@ -337,7 +334,8 @@ final public class SettingsService extends Binder { try { Bundle arg = new Bundle(); arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); - Bundle b = provider.call(resolveCallingPackage(), callGetCommand, key, arg); + Bundle b = provider.call(resolveCallingPackage(), Settings.AUTHORITY, + callGetCommand, key, arg); if (b != null) { result = b.getPairValue(); } @@ -374,7 +372,8 @@ final public class SettingsService extends Binder { if (makeDefault) { arg.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true); } - provider.call(resolveCallingPackage(), callPutCommand, key, arg); + provider.call(resolveCallingPackage(), Settings.AUTHORITY, + callPutCommand, key, arg); } catch (RemoteException e) { throw new RuntimeException("Failed in IPC", e); } @@ -397,8 +396,8 @@ final public class SettingsService extends Binder { try { Bundle arg = new Bundle(); arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); - Bundle result = - provider.call(resolveCallingPackage(), callDeleteCommand, key, arg); + Bundle result = provider.call(resolveCallingPackage(), Settings.AUTHORITY, + callDeleteCommand, key, arg); return result.getInt(SettingsProvider.RESULT_ROWS_DELETED); } catch (RemoteException e) { throw new RuntimeException("Failed in IPC", e); @@ -424,7 +423,7 @@ final public class SettingsService extends Binder { } String packageName = mPackageName != null ? mPackageName : resolveCallingPackage(); arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle); - provider.call(packageName, callResetCommand, null, arg); + provider.call(packageName, Settings.AUTHORITY, callResetCommand, null, arg); } catch (RemoteException e) { throw new RuntimeException("Failed in IPC", e); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index ae2ca3f87ae7..521163f50ca1 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -377,6 +377,13 @@ final class SettingsState { @GuardedBy("mLock") public boolean insertSettingLocked(String name, String value, String tag, boolean makeDefault, String packageName) { + return insertSettingLocked(name, value, tag, makeDefault, false, packageName); + } + + // The settings provider must hold its lock when calling here. + @GuardedBy("mLock") + public boolean insertSettingLocked(String name, String value, String tag, + boolean makeDefault, boolean forceNonSystemPackage, String packageName) { if (TextUtils.isEmpty(name)) { return false; } @@ -387,7 +394,7 @@ final class SettingsState { Setting newState; if (oldState != null) { - if (!oldState.update(value, makeDefault, packageName, tag, false)) { + if (!oldState.update(value, makeDefault, packageName, tag, forceNonSystemPackage)) { return false; } newState = oldState; diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java new file mode 100644 index 000000000000..9d0462e14b63 --- /dev/null +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2018 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.providers.settings; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; + +import android.content.ContentResolver; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import libcore.io.Streams; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Tests for {@link DeviceConfigService}. + */ +@RunWith(AndroidJUnit4.class) +public class DeviceConfigServiceTest { + /** + * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System + * API. + */ + private static final Uri CONFIG_CONTENT_URI = + Uri.parse("content://" + Settings.AUTHORITY + "/config"); + private static final String sNamespace = "namespace1"; + private static final String sKey = "key1"; + private static final String sValue = "value1"; + + private ContentResolver mContentResolver; + + @Before + public void setUp() { + mContentResolver = InstrumentationRegistry.getContext().getContentResolver(); + } + + @After + public void cleanUp() { + deleteFromContentProvider(mContentResolver, sNamespace, sKey); + } + + @Test + public void testPut() throws Exception { + final String newNamespace = "namespace2"; + final String newValue = "value2"; + + String result = getFromContentProvider(mContentResolver, sNamespace, sKey); + assertNull(result); + + try { + executeShellCommand("device_config put " + sNamespace + " " + sKey + " " + sValue); + executeShellCommand("device_config put " + newNamespace + " " + sKey + " " + newValue); + + result = getFromContentProvider(mContentResolver, sNamespace, sKey); + assertEquals(sValue, result); + result = getFromContentProvider(mContentResolver, newNamespace, sKey); + assertEquals(newValue, result); + } finally { + deleteFromContentProvider(mContentResolver, newNamespace, sKey); + } + } + + @Test + public void testPut_invalidArgs() throws Exception { + // missing sNamespace + executeShellCommand("device_config put " + sKey + " " + sValue); + String result = getFromContentProvider(mContentResolver, sNamespace, sKey); + // still null + assertNull(result); + + // too many arguments + executeShellCommand( + "device_config put " + sNamespace + " " + sKey + " " + sValue + " extra_arg"); + result = getFromContentProvider(mContentResolver, sNamespace, sKey); + // still null + assertNull(result); + } + + @Test + public void testDelete() throws Exception { + final String newNamespace = "namespace2"; + + putWithContentProvider(mContentResolver, sNamespace, sKey, sValue); + putWithContentProvider(mContentResolver, newNamespace, sKey, sValue); + String result = getFromContentProvider(mContentResolver, sNamespace, sKey); + assertEquals(sValue, result); + result = getFromContentProvider(mContentResolver, newNamespace, sKey); + assertEquals(sValue, result); + + try { + executeShellCommand("device_config delete " + sNamespace + " " + sKey); + // sKey is deleted from sNamespace + result = getFromContentProvider(mContentResolver, sNamespace, sKey); + assertNull(result); + // sKey is not deleted from newNamespace + result = getFromContentProvider(mContentResolver, newNamespace, sKey); + assertEquals(sValue, result); + } finally { + deleteFromContentProvider(mContentResolver, newNamespace, sKey); + } + } + + @Test + public void testDelete_invalidArgs() throws Exception { + putWithContentProvider(mContentResolver, sNamespace, sKey, sValue); + String result = getFromContentProvider(mContentResolver, sNamespace, sKey); + assertEquals(sValue, result); + + // missing sNamespace + executeShellCommand("device_config delete " + sKey); + result = getFromContentProvider(mContentResolver, sNamespace, sKey); + // sValue was not deleted + assertEquals(sValue, result); + + // too many arguments + executeShellCommand("device_config delete " + sNamespace + " " + sKey + " extra_arg"); + result = getFromContentProvider(mContentResolver, sNamespace, sKey); + // sValue was not deleted + assertEquals(sValue, result); + } + + @Test + public void testReset() throws Exception { + String newValue = "value2"; + + // make sValue the default value + executeShellCommand( + "device_config put " + sNamespace + " " + sKey + " " + sValue + " default"); + // make newValue the current value + executeShellCommand( + "device_config put " + sNamespace + " " + sKey + " " + newValue); + String result = getFromContentProvider(mContentResolver, sNamespace, sKey); + assertEquals(newValue, result); + + // reset values that were set by untrusted packages + executeShellCommand("device_config reset untrusted_defaults " + sNamespace); + result = getFromContentProvider(mContentResolver, sNamespace, sKey); + // the default value has been restored + assertEquals(sValue, result); + + // clear values that were set by untrusted packages + executeShellCommand("device_config reset trusted_defaults " + sNamespace); + result = getFromContentProvider(mContentResolver, sNamespace, sKey); + // even the default value is gone now + assertNull(result); + } + + private static void executeShellCommand(String command) throws IOException { + InputStream is = new FileInputStream(InstrumentationRegistry.getInstrumentation() + .getUiAutomation().executeShellCommand(command).getFileDescriptor()); + Streams.readFully(is); + } + + private static void putWithContentProvider(ContentResolver resolver, String namespace, + String key, String value) { + putWithContentProvider(resolver, namespace, key, value, false); + } + + private static void putWithContentProvider(ContentResolver resolver, String namespace, + String key, String value, boolean makeDefault) { + String compositeName = namespace + "/" + key; + Bundle args = new Bundle(); + args.putString(Settings.NameValueTable.VALUE, value); + if (makeDefault) { + args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true); + } + resolver.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args); + } + + private static String getFromContentProvider(ContentResolver resolver, String namespace, + String key) { + String compositeName = namespace + "/" + key; + Bundle result = resolver.call( + CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null); + assertNotNull(result); + return result.getString(Settings.NameValueTable.VALUE); + } + + private static boolean deleteFromContentProvider(ContentResolver resolver, String namespace, + String key) { + String compositeName = namespace + "/" + key; + Bundle result = resolver.call( + CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null); + assertNotNull(result); + return compositeName.equals(result.getString(Settings.NameValueTable.VALUE)); + } +} diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 83e83693375b..5fe08aab93cc 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -51,6 +51,7 @@ <uses-permission android:name="android.permission.REAL_GET_TASKS" /> <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> <uses-permission android:name="android.permission.REORDER_TASKS" /> + <uses-permission android:name="android.permission.REMOVE_TASKS" /> <uses-permission android:name="android.permission.SET_ANIMATION_SCALE" /> <uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> @@ -130,7 +131,7 @@ <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> <uses-permission android:name="android.permission.MANAGE_AUTO_FILL" /> - <uses-permission android:name="android.permission.MANAGE_SMART_SUGGESTIONS" /> + <uses-permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" /> <uses-permission android:name="android.permission.NETWORK_SETTINGS" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.SET_TIME" /> diff --git a/packages/Shell/res/values-mr/strings.xml b/packages/Shell/res/values-mr/strings.xml index dc6a6126bc5d..9595e28eb6e2 100644 --- a/packages/Shell/res/values-mr/strings.xml +++ b/packages/Shell/res/values-mr/strings.xml @@ -28,7 +28,7 @@ <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"तुमचा बग रीपोर्ट स्क्रीनशॉटशिवाय शेअर करण्यासाठी टॅप करा किंवा स्क्रीनशॉट पूर्ण होण्याची प्रतीक्षा करा"</string> <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"स्क्रीनशॉट शिवाय तुमचा बग रीपोर्ट शेअर करण्यासाठी टॅप करा किंवा समाप्त करण्यासाठी स्क्रीनशॉटची प्रतीक्षा करा"</string> <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"स्क्रीनशॉट शिवाय तुमचा बग रीपोर्ट शेअर करण्यासाठी टॅप करा किंवा समाप्त करण्यासाठी स्क्रीनशॉटची प्रतीक्षा करा"</string> - <string name="bugreport_confirm" msgid="5917407234515812495">"बग रीपोर्टांमध्ये तुम्ही संवेदनशील (अॅप-वापर आणि स्थान डेटा यासारखा) डेटा म्हणून विचार करता त्या डेटाच्या समावेशासह सिस्टीमच्या विविध लॉग फायलींमधील डेटा असतो. ज्या लोकांवर आणि अॅपवर तुमचा विश्वास आहे केवळ त्यांच्यासह हा बग रीपोर्ट शेअर करा."</string> + <string name="bugreport_confirm" msgid="5917407234515812495">"बग रीपोर्टांमध्ये तुम्ही संवेदनशील (अॅप-वापर आणि स्थान डेटा यासारखा) डेटा म्हणून विचार करता त्या डेटाच्या समावेशासह सिस्टीमच्या विविध लॉग फायलींमधील डेटा असतो. ज्या लोकांवर आणि अॅपवर तुमचा विश्वास आहे केवळ त्यांच्यासह हा बग रीपोर्ट शेअर करा."</string> <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"पुन्हा दर्शवू नका"</string> <string name="bugreport_storage_title" msgid="5332488144740527109">"बग रीपोर्ट"</string> <string name="bugreport_unreadable_text" msgid="586517851044535486">"बग रीपोर्ट फाईल वाचणे शक्य झाले नाही"</string> diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java index 7cb63ea7f151..1a18f6096e25 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java @@ -18,6 +18,8 @@ import android.view.View; import com.android.systemui.plugins.annotations.ProvidesInterface; +import java.util.TimeZone; + /** * This plugin is used to replace main clock in keyguard. */ @@ -55,4 +57,9 @@ public interface ClockPlugin extends Plugin { * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. */ default void setDarkAmount(float darkAmount) {} + + /** + * Notifies that the time zone has changed. + */ + default void onTimeZoneChanged(TimeZone timeZone) {} } diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 555f443ff38e..9245c300acf5 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -31,7 +31,7 @@ <!-- Max Height of the sliding KeyguardSecurityContainer (includes 2x keyguard_security_view_top_margin) --> - <dimen name="keyguard_security_max_height">450dp</dimen> + <dimen name="keyguard_security_max_height">455dp</dimen> <!-- Margin around the various security views --> <dimen name="keyguard_security_view_top_margin">8dp</dimen> diff --git a/packages/SystemUI/res/layout/bubble_expanded_view.xml b/packages/SystemUI/res/layout/bubble_expanded_view.xml new file mode 100644 index 000000000000..b2307e71e3ce --- /dev/null +++ b/packages/SystemUI/res/layout/bubble_expanded_view.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<com.android.systemui.bubbles.BubbleExpandedViewContainer + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:id="@+id/bubble_expanded_view"> + + <!-- TODO: header --> + + <View + android:id="@+id/pointer_view" + android:layout_width="@dimen/bubble_pointer_width" + android:layout_height="@dimen/bubble_pointer_height" + /> +</com.android.systemui.bubbles.BubbleExpandedViewContainer> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 7ea4027fc7f1..6fb44ccf0e07 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -89,7 +89,7 @@ <string name="usb_preference_title" msgid="6551050377388882787">"USB फाईल स्थानांतरण पर्याय"</string> <string name="use_mtp_button_title" msgid="4333504413563023626">"मीडिया प्लेअर म्हणून माउंट करा (MTP)"</string> <string name="use_ptp_button_title" msgid="7517127540301625751">"कॅमेरा म्हणून माउंट करा (PTP)"</string> - <string name="installer_cd_button_title" msgid="2312667578562201583">"Mac साठी Android फाईल स्थानांतर अॅप इंस्टॉल करा"</string> + <string name="installer_cd_button_title" msgid="2312667578562201583">"Mac साठी Android फाईल स्थानांतर अॅप इंस्टॉल करा"</string> <string name="accessibility_back" msgid="567011538994429120">"मागे"</string> <string name="accessibility_home" msgid="8217216074895377641">"होम"</string> <string name="accessibility_menu" msgid="316839303324695949">"मेनू"</string> @@ -598,7 +598,7 @@ <string name="tuner_full_importance_settings" msgid="3207312268609236827">"पॉवर सूचना नियंत्रणे"</string> <string name="tuner_full_importance_settings_on" msgid="7545060756610299966">"चालू"</string> <string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"बंद"</string> - <string name="power_notification_controls_description" msgid="4372459941671353358">"पॉवर सूचना नियंत्रणांच्या साहाय्याने तुम्ही अॅप सूचनांसाठी 0 ते 5 असे महत्त्व स्तर सेट करू शकता. \n\n"<b>"स्तर 5"</b>" \n- सूचना सूचीच्या शीर्षस्थानी दाखवा \n- फुल स्क्रीन व्यत्ययास अनुमती द्या \n- नेहमी डोकावून पहा \n\n"<b>"स्तर 4"</b>\n" - फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- नेहमी डोकावून पहा \n\n"<b>"स्तर 3"</b>" \n- फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n\n"<b>"स्तर 2"</b>" \n- फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n- कधीही ध्वनी किंवा व्हायब्रेट करू नका \n\n"<b>"स्तर 1"</b>\n"- फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n- कधीही ध्वनी किंवा व्हायब्रेट करू नका \n- लॉक स्क्रीन आणि स्टेटस बार मधून लपवा \n- सूचना सूचीच्या तळाशी दर्शवा \n\n"<b>"स्तर 0"</b>" \n- अॅपमधील सर्व सूचना ब्लॉक करा"</string> + <string name="power_notification_controls_description" msgid="4372459941671353358">"पॉवर सूचना नियंत्रणांच्या साहाय्याने तुम्ही अॅप सूचनांसाठी 0 ते 5 असे महत्त्व स्तर सेट करू शकता. \n\n"<b>"स्तर 5"</b>" \n- सूचना सूचीच्या शीर्षस्थानी दाखवा \n- फुल स्क्रीन व्यत्ययास अनुमती द्या \n- नेहमी डोकावून पहा \n\n"<b>"स्तर 4"</b>\n" - फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- नेहमी डोकावून पहा \n\n"<b>"स्तर 3"</b>" \n- फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n\n"<b>"स्तर 2"</b>" \n- फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n- कधीही ध्वनी किंवा व्हायब्रेट करू नका \n\n"<b>"स्तर 1"</b>\n"- फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n- कधीही ध्वनी किंवा व्हायब्रेट करू नका \n- लॉक स्क्रीन आणि स्टेटस बार मधून लपवा \n- सूचना सूचीच्या तळाशी दर्शवा \n\n"<b>"स्तर 0"</b>" \n- अॅपमधील सर्व सूचना ब्लॉक करा"</string> <string name="notification_header_default_channel" msgid="7506845022070889909">"सूचना"</string> <string name="notification_channel_disabled" msgid="344536703863700565">"आता तुम्हाला या सूचना दिसणार नाहीत"</string> <string name="notification_channel_minimized" msgid="1664411570378910931">"या सूचना लहान केल्या जातील"</string> @@ -621,13 +621,13 @@ <string name="inline_keep_showing_app" msgid="1723113469580031041">"या अॅपकडील सूचना दाखवणे सुरू ठेवायचे?"</string> <string name="notification_unblockable_desc" msgid="1037434112919403708">"या सूचना बंद करता येत नाहीत"</string> <string name="notification_delegate_header" msgid="9167022191405284627">"<xliff:g id="APP_NAME">%1$s</xliff:g> मार्गे"</string> - <string name="appops_camera" msgid="8100147441602585776">"हे अॅप कॅमेरा वापरत आहे."</string> - <string name="appops_microphone" msgid="741508267659494555">"हे अॅप मायक्रोफोन वापरत आहे."</string> - <string name="appops_overlay" msgid="6165912637560323464">"हे अॅप स्क्रीनवरील इतर अॅप्स वर प्रदर्शित होत आहे."</string> - <string name="appops_camera_mic" msgid="1576901651150187433">"हे अॅप मायक्रोफोन आणि कॅमेरा वापरत आहे."</string> - <string name="appops_camera_overlay" msgid="8869400080809298814">"हे अॅप तुमच्या स्क्रीनवरील इतर अॅप्स वर प्रदर्शित होत आहे आणि कॅमेरा वापरत आहे."</string> - <string name="appops_mic_overlay" msgid="4835157962857919804">"हे अॅप तुमच्या स्क्रीनवरील इतर अॅप्स वर प्रदर्शित होत आहे आणि मायक्रोफोन वापरत आहे."</string> - <string name="appops_camera_mic_overlay" msgid="6718768197048030993">"हे अॅप तुमच्या स्क्रीनवरील इतर अॅप्स वर प्रदर्शित होत आहे आणि मायक्रोफोन आणि कॅमेरा वापरत आहे."</string> + <string name="appops_camera" msgid="8100147441602585776">"हे अॅप कॅमेरा वापरत आहे."</string> + <string name="appops_microphone" msgid="741508267659494555">"हे अॅप मायक्रोफोन वापरत आहे."</string> + <string name="appops_overlay" msgid="6165912637560323464">"हे अॅप स्क्रीनवरील इतर अॅप्स वर प्रदर्शित होत आहे."</string> + <string name="appops_camera_mic" msgid="1576901651150187433">"हे अॅप मायक्रोफोन आणि कॅमेरा वापरत आहे."</string> + <string name="appops_camera_overlay" msgid="8869400080809298814">"हे अॅप तुमच्या स्क्रीनवरील इतर अॅप्स वर प्रदर्शित होत आहे आणि कॅमेरा वापरत आहे."</string> + <string name="appops_mic_overlay" msgid="4835157962857919804">"हे अॅप तुमच्या स्क्रीनवरील इतर अॅप्स वर प्रदर्शित होत आहे आणि मायक्रोफोन वापरत आहे."</string> + <string name="appops_camera_mic_overlay" msgid="6718768197048030993">"हे अॅप तुमच्या स्क्रीनवरील इतर अॅप्स वर प्रदर्शित होत आहे आणि मायक्रोफोन आणि कॅमेरा वापरत आहे."</string> <string name="notification_appops_settings" msgid="1028328314935908050">"सेटिंग्ज"</string> <string name="notification_appops_ok" msgid="1156966426011011434">"ओके"</string> <string name="notification_channel_controls_opened_accessibility" msgid="6553950422055908113">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी सूचना नियंत्रणे खुली आहेत"</string> @@ -776,8 +776,8 @@ <string name="accessibility_qs_edit_tile_move" msgid="3108103090006972938">"<xliff:g id="TILE_NAME">%1$s</xliff:g> <xliff:g id="POSITION">%2$d</xliff:g> स्थानावर हलवा"</string> <string name="accessibility_desc_quick_settings_edit" msgid="8073587401747016103">"द्रुत सेटिंग्ज संपादक."</string> <string name="accessibility_desc_notification_icon" msgid="8352414185263916335">"<xliff:g id="ID_1">%1$s</xliff:g> सूचना: <xliff:g id="ID_2">%2$s</xliff:g>"</string> - <string name="dock_forced_resizable" msgid="5914261505436217520">"अॅप कदाचित विभाजित-स्क्रीनसह कार्य करू शकत नाही."</string> - <string name="dock_non_resizeble_failed_to_dock_text" msgid="3871617304250207291">"अॅप स्क्रीन-विभाजनास समर्थन देत नाही."</string> + <string name="dock_forced_resizable" msgid="5914261505436217520">"अॅप कदाचित विभाजित-स्क्रीनसह कार्य करू शकत नाही."</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="3871617304250207291">"अॅप स्क्रीन-विभाजनास समर्थन देत नाही."</string> <string name="forced_resizable_secondary_display" msgid="4230857851756391925">"दुसऱ्या डिस्प्लेवर अॅप कदाचित चालणार नाही."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="7793821742158306742">"दुसऱ्या डिस्प्लेवर अॅप लाँच होणार नाही."</string> <string name="accessibility_quick_settings_settings" msgid="6132460890024942157">"सेटिंग्ज उघडा."</string> @@ -806,7 +806,7 @@ <string name="pip_skip_to_prev" msgid="1955311326688637914">"डावलून मागे जा"</string> <string name="thermal_shutdown_title" msgid="4458304833443861111">"तापल्यामुळे फोन बंद झाला"</string> <string name="thermal_shutdown_message" msgid="9006456746902370523">"तुमचा फोन आता व्यवस्थित चालू आहे"</string> - <string name="thermal_shutdown_dialog_message" msgid="566347880005304139">"तुमचा फोन खूप तापलाय, म्हणून तो थंड होण्यासाठी बंद झाला आहे. तुमचा फोन आता व्यवस्थित चालू आहे.\n\nतुम्ही असे केल्यास तुमचा फोन खूप तापेल:\n •संसाधन केंद्रित अॅप वापरणे (गेमिंग, व्हिडिओ किंवा नेव्हिगेशन अॅप यासारखे)\n •मोठ्या फायली डाउनलोड किंवा अपलोड करणे\n •उच्च तापमानामध्ये तुमचा फोन वापरणे"</string> + <string name="thermal_shutdown_dialog_message" msgid="566347880005304139">"तुमचा फोन खूप तापलाय, म्हणून तो थंड होण्यासाठी बंद झाला आहे. तुमचा फोन आता व्यवस्थित चालू आहे.\n\nतुम्ही असे केल्यास तुमचा फोन खूप तापेल:\n •संसाधन केंद्रित अॅप वापरणे (गेमिंग, व्हिडिओ किंवा नेव्हिगेशन अॅप यासारखे)\n •मोठ्या फायली डाउनलोड किंवा अपलोड करणे\n •उच्च तापमानामध्ये तुमचा फोन वापरणे"</string> <string name="high_temp_title" msgid="4589508026407318374">"फोन ऊष्ण होत आहे"</string> <string name="high_temp_notif_message" msgid="5642466103153429279">"फोन थंड होत असताना काही वैशिष्ट्ये मर्यादित असतात"</string> <string name="high_temp_dialog_message" msgid="6840700639374113553">"तुमचा फोन स्वयंचलितपणे थंड होईल. तुम्ही अद्यापही तुमचा फोन वापरू शकता परंतु तो कदाचित धीमेपणे कार्य करेल.\n\nतुमचा फोन एकदा थंड झाला की, तो सामान्यपणे कार्य करेल."</string> @@ -832,9 +832,9 @@ <string name="notification_channel_hints" msgid="7323870212489152689">"सूचना"</string> <string name="instant_apps" msgid="6647570248119804907">"इन्सटंट अॅप्स"</string> <string name="instant_apps_title" msgid="8738419517367449783">"<xliff:g id="APP">%1$s</xliff:g> रन होत आहे"</string> - <string name="instant_apps_message" msgid="1183313016396018086">"इंस्टॉल केल्याशिवाय अॅप उघडले."</string> - <string name="instant_apps_message_with_help" msgid="6179830437630729747">"इंस्टॉल केल्याशिवाय अॅप उघडले. अधिक जाणून घेण्यासाठी टॅप करा."</string> - <string name="app_info" msgid="6856026610594615344">"अॅप माहिती"</string> + <string name="instant_apps_message" msgid="1183313016396018086">"इंस्टॉल केल्याशिवाय अॅप उघडले."</string> + <string name="instant_apps_message_with_help" msgid="6179830437630729747">"इंस्टॉल केल्याशिवाय अॅप उघडले. अधिक जाणून घेण्यासाठी टॅप करा."</string> + <string name="app_info" msgid="6856026610594615344">"अॅप माहिती"</string> <string name="go_to_web" msgid="2650669128861626071">"ब्राउझरवर जा"</string> <string name="mobile_data" msgid="7094582042819250762">"मोबाइल डेटा"</string> <string name="mobile_data_text_format" msgid="3526214522670876454">"<xliff:g id="ID_1">%1$s</xliff:g> — <xliff:g id="ID_2">%2$s</xliff:g>"</string> @@ -873,7 +873,7 @@ <!-- no translation found for ongoing_privacy_chip_content_multiple_apps_single_op (4871926099254314088) --> <string name="ongoing_privacy_dialog_cancel" msgid="5479124524931216790">"रद्द करा"</string> <string name="ongoing_privacy_dialog_open_settings" msgid="2074844974365194279">"तपशील पाहा"</string> - <string name="ongoing_privacy_dialog_single_app_title" msgid="6019646962021696632">"अॅप तुमचे <xliff:g id="TYPES_LIST">%s</xliff:g> वापरत आहे"</string> + <string name="ongoing_privacy_dialog_single_app_title" msgid="6019646962021696632">"अॅप तुमचे <xliff:g id="TYPES_LIST">%s</xliff:g> वापरत आहे"</string> <string name="ongoing_privacy_dialog_multiple_apps_title" msgid="8013356222977903365">"अॅप्स तुमचे <xliff:g id="TYPES_LIST">%s</xliff:g> वापरत आहेत"</string> <string name="ongoing_privacy_dialog_separator" msgid="6854860652480837439">", "</string> <string name="ongoing_privacy_dialog_last_separator" msgid="2400503446627122483">" आणि "</string> @@ -882,6 +882,6 @@ <string name="privacy_type_microphone" msgid="4153045784928554506">"मायक्रोफोन"</string> <plurals name="ongoing_privacy_dialog_overflow_text" formatted="false" msgid="3441296594927649172"> <item quantity="one">इतर <xliff:g id="NUM_APPS_1">%d</xliff:g> अॅप</item> - <item quantity="other">इतर <xliff:g id="NUM_APPS_1">%d</xliff:g> अॅप्स</item> + <item quantity="other">इतर <xliff:g id="NUM_APPS_1">%d</xliff:g> अॅप्स</item> </plurals> </resources> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 4f5b23e2b546..558fd9b7382d 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -785,7 +785,7 @@ <string name="accessibility_quick_settings_collapse" msgid="1792625797142648105">"Lukk hurtiginnstillingene."</string> <string name="accessibility_quick_settings_alarm_set" msgid="1863000242431528676">"Alarm er angitt."</string> <string name="accessibility_quick_settings_user" msgid="1567445362870421770">"Logget på som <xliff:g id="ID_1">%s</xliff:g>"</string> - <string name="data_connection_no_internet" msgid="4503302451650972989">"Ingen Internett-tilkobling"</string> + <string name="data_connection_no_internet" msgid="4503302451650972989">"Ingen internettilkobling"</string> <string name="accessibility_quick_settings_open_details" msgid="4230931801728005194">"Åpne informasjonen."</string> <string name="accessibility_quick_settings_not_available" msgid="4190068184294019846">"Utilgjengelig fordi <xliff:g id="REASON">%s</xliff:g>"</string> <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Åpne <xliff:g id="ID_1">%s</xliff:g>-innstillingene."</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 5e7a1198b962..06467b85a687 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -651,7 +651,7 @@ <item quantity="other">%d minuten</item> <item quantity="one">%d minuut</item> </plurals> - <string name="battery_panel_title" msgid="7944156115535366613">"Accugebruik"</string> + <string name="battery_panel_title" msgid="7944156115535366613">"Batterijgebruik"</string> <string name="battery_detail_charging_summary" msgid="1279095653533044008">"Batterijbesparing niet beschikbaar tijdens opladen"</string> <string name="battery_detail_switch_title" msgid="6285872470260795421">"Batterijbesparing"</string> <string name="battery_detail_switch_summary" msgid="9049111149407626804">"Vermindert de prestaties en achtergrondgegevens"</string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 8e0bfb65428e..3851190fdeec 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -23,6 +23,8 @@ <dimen name="navigation_bar_size">@*android:dimen/navigation_bar_height</dimen> <!-- Minimum swipe distance to catch the swipe gestures to invoke assist or switch tasks. --> <dimen name="navigation_bar_min_swipe_distance">48dp</dimen> + <!-- The distance from a side of device of the navigation bar to start an edge swipe --> + <dimen name="navigation_bar_edge_swipe_threshold">60dp</dimen> <!-- thickness (height) of the dead zone at the top of the navigation bar, reducing false presses on navbar buttons; approx 2mm --> @@ -359,6 +361,7 @@ <!-- The height of the qs customize header. Should be (28dp - qs_tile_margin_top_bottom). --> <dimen name="qs_customize_header_min_height">40dp</dimen> <dimen name="qs_tile_margin_top">18dp</dimen> + <dimen name="qs_tile_background_size">40dp</dimen> <dimen name="qs_quick_tile_size">48dp</dimen> <!-- Maximum width of quick quick settings panel. Defaults to MATCH_PARENT--> <dimen name="qs_quick_layout_width">-1px</dimen> @@ -991,4 +994,10 @@ <dimen name="bubble_icon_size">24dp</dimen> <!-- Default height of the expanded view shown when the bubble is expanded --> <dimen name="bubble_expanded_default_height">400dp</dimen> + <!-- Height of the triangle that points to the expanded bubble --> + <dimen name="bubble_pointer_height">4dp</dimen> + <!-- Width of the triangle that points to the expanded bubble --> + <dimen name="bubble_pointer_width">6dp</dimen> + <!-- Extra padding around the dismiss target for bubbles --> + <dimen name="bubble_dismiss_slop">16dp</dimen> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java index a9cf857c3e50..807edf624db8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplier.java @@ -25,6 +25,8 @@ import android.view.SurfaceControl.Transaction; import android.view.View; import android.view.ViewRootImpl; +import java.util.function.Consumer; + /** * Helper class to apply surface transactions in sync with RenderThread. */ @@ -78,6 +80,35 @@ public class SyncRtSurfaceTransactionApplier { applyParams(t.mTransaction, params, t.mTmpValues); } + /** + * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is + * attached if necessary. + */ + public static void create(final View targetView, + final Consumer<SyncRtSurfaceTransactionApplier> callback) { + if (targetView == null) { + // No target view, no applier + callback.accept(null); + } else if (targetView.getViewRootImpl() != null) { + // Already attached, we're good to go + callback.accept(new SyncRtSurfaceTransactionApplier(targetView)); + } else { + // Haven't been attached before we can get the view root + targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + targetView.removeOnAttachStateChangeListener(this); + callback.accept(new SyncRtSurfaceTransactionApplier(targetView)); + } + + @Override + public void onViewDetachedFromWindow(View v) { + // Do nothing + } + }); + } + } + private static void applyParams(Transaction t, SurfaceParams params, float[] tmpFloat9) { t.setMatrix(params.surface, params.matrix, tmpFloat9); t.setWindowCrop(params.surface, params.windowCrop); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index b439c6c9c186..0ec90148c350 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -17,6 +17,7 @@ import com.android.systemui.plugins.PluginListener; import com.android.systemui.shared.plugins.PluginManager; import java.util.Objects; +import java.util.TimeZone; /** * Switch to show plugin clock when plugin is connected, otherwise it will show default clock. @@ -162,6 +163,15 @@ public class KeyguardClockSwitch extends FrameLayout { } /** + * Notifies that the time zone has changed. + */ + public void onTimeZoneChanged(TimeZone timeZone) { + if (mClockPlugin != null) { + mClockPlugin.onTimeZoneChanged(timeZone); + } + } + + /** * When plugin changes, set all kept parameters into newer plugin. */ private void initPluginParams() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 6b24ad56d01c..41afa9a21128 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -193,7 +193,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView public void onClick(View v) { mCallback.userActivity(); // Leave the screen on a bit longer // Do not show auxiliary subtypes in password lock screen. - mImm.showInputMethodPicker(false /* showAuxiliarySubtypes */); + mImm.showInputMethodPickerFromSystem(false /* showAuxiliarySubtypes */, + getContext().getDisplayId()); } }); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index be795d2b6f2c..1e9d288bc605 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -51,6 +51,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.google.android.collect.Sets; import java.util.Locale; +import java.util.TimeZone; public class KeyguardStatusView extends GridLayout implements ConfigurationController.ConfigurationListener, View.OnLayoutChangeListener { @@ -85,6 +86,11 @@ public class KeyguardStatusView extends GridLayout implements } @Override + public void onTimeZoneChanged(TimeZone timeZone) { + updateTimeZone(timeZone); + } + + @Override public void onKeyguardVisibilityChanged(boolean showing) { if (showing) { if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing); @@ -282,6 +288,10 @@ public class KeyguardStatusView extends GridLayout implements mClockView.refresh(); } + private void updateTimeZone(TimeZone timeZone) { + mClockView.onTimeZoneChanged(timeZone); + } + private void refreshFormat() { Patterns.update(mContext); mClockView.setFormat12Hour(Patterns.clockView12); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 904f94486e6c..416441e33a05 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -98,6 +98,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map.Entry; +import java.util.TimeZone; /** * Watches for updates that may be interesting to the keyguard, and provides @@ -151,6 +152,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { private static final int MSG_BIOMETRIC_AUTHENTICATION_CONTINUE = 336; private static final int MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED = 337; private static final int MSG_TELEPHONY_CAPABLE = 338; + private static final int MSG_TIMEZONE_UPDATE = 339; /** Biometric authentication state: Not listening. */ private static final int BIOMETRIC_STATE_STOPPED = 0; @@ -260,6 +262,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { case MSG_TIME_UPDATE: handleTimeUpdate(); break; + case MSG_TIMEZONE_UPDATE: + handleTimeZoneUpdate((String) msg.obj); + break; case MSG_BATTERY_UPDATE: handleBatteryUpdate((BatteryStatus) msg.obj); break; @@ -964,9 +969,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { if (DEBUG) Log.d(TAG, "received broadcast " + action); if (Intent.ACTION_TIME_TICK.equals(action) - || Intent.ACTION_TIME_CHANGED.equals(action) - || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { + || Intent.ACTION_TIME_CHANGED.equals(action)) { mHandler.sendEmptyMessage(MSG_TIME_UPDATE); + } else if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { + final Message msg = mHandler.obtainMessage( + MSG_TIMEZONE_UPDATE, intent.getStringExtra("time-zone")); + mHandler.sendMessage(msg); } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN); final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0); @@ -1860,6 +1868,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } /** + * Handle (@line #MSG_TIMEZONE_UPDATE} + */ + private void handleTimeZoneUpdate(String timeZone) { + if (DEBUG) Log.d(TAG, "handleTimeZoneUpdate"); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onTimeZoneChanged(TimeZone.getTimeZone(timeZone)); + // Also notify callbacks about time change to remain compatible. + cb.onTimeChanged(); + } + } + } + + /** * Handle {@link #MSG_BATTERY_UPDATE} */ private void handleBatteryUpdate(BatteryStatus status) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index f818d05f2c6d..8696bb769bbd 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -26,6 +26,8 @@ import android.view.WindowManagerPolicyConstants; import com.android.internal.telephony.IccCardConstants; import com.android.systemui.statusbar.KeyguardIndicationController; +import java.util.TimeZone; + /** * Callback for general information relevant to lock screen. */ @@ -49,6 +51,13 @@ public class KeyguardUpdateMonitorCallback { public void onTimeChanged() { } /** + * Called when time zone changes. + * + * @note When time zone changes, onTimeChanged will be called too. + */ + public void onTimeZoneChanged(TimeZone timeZone) { } + + /** * Called when the carrier PLMN or SPN changes. */ public void onRefreshCarrierInfo() { } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 5c259d5c4093..1e91ef3f10c7 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -32,6 +32,7 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.notification.NotificationData; @@ -72,7 +73,8 @@ public class BubbleController { private Point mDisplaySize; // Bubbles get added to the status bar view - private StatusBarWindowController mStatusBarWindowController; + @VisibleForTesting + protected StatusBarWindowController mStatusBarWindowController; // Used for determining view rect for touch interaction private Rect mTempRect = new Rect(); @@ -302,6 +304,11 @@ public class BubbleController { return mTempRect; } + @VisibleForTesting + public BubbleStackView getStackView() { + return mStackView; + } + // TODO: factor in PIP location / maybe last place user had it /** * Gets an appropriate starting point to position the bubble stack. diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java new file mode 100644 index 000000000000..e28d96b2def9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2018 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.bubbles; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.drawable.ShapeDrawable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; + +import com.android.systemui.R; +import com.android.systemui.recents.TriangleShape; + +/** + * Container for the expanded bubble view, handles rendering the caret and header of the view. + */ +public class BubbleExpandedViewContainer extends LinearLayout { + + // The triangle pointing to the expanded view + private View mPointerView; + // The view that is being displayed for the expanded state + private View mExpandedView; + + public BubbleExpandedViewContainer(Context context) { + this(context, null); + } + + public BubbleExpandedViewContainer(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BubbleExpandedViewContainer(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public BubbleExpandedViewContainer(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setOrientation(VERTICAL); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + Resources res = getResources(); + mPointerView = findViewById(R.id.pointer_view); + int width = res.getDimensionPixelSize(R.dimen.bubble_pointer_width); + int height = res.getDimensionPixelSize(R.dimen.bubble_pointer_height); + ShapeDrawable triangleDrawable = new ShapeDrawable( + TriangleShape.create(width, height, true /* pointUp */)); + triangleDrawable.setTint(Color.WHITE); // TODO: dark mode + mPointerView.setBackground(triangleDrawable); + } + + /** + * Set the x position that the tip of the triangle should point to. + */ + public void setPointerPosition(int x) { + // Adjust for the pointer size + x -= (mPointerView.getWidth() / 2); + mPointerView.setTranslationX(x); + } + + /** + * Set the view to display for the expanded state. Passing null will clear the view. + */ + public void setExpandedView(View view) { + if (mExpandedView != null) { + removeView(mExpandedView); + } + mExpandedView = view; + if (mExpandedView != null) { + addView(mExpandedView); + } + } + + /** + * @return the view containing the expanded content, can be null. + */ + @Nullable + public View getExpandedView() { + return mExpandedView; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index e395c4c2765c..dfd18b23a5e3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.RectF; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -52,7 +53,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F private Point mDisplaySize; private FrameLayout mBubbleContainer; - private FrameLayout mExpandedViewContainer; + private BubbleExpandedViewContainer mExpandedViewContainer; private int mBubbleSize; private int mBubblePadding; @@ -111,7 +112,9 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F int padding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); - mExpandedViewContainer = new FrameLayout(context); + mExpandedViewContainer = (BubbleExpandedViewContainer) + LayoutInflater.from(context).inflate(R.layout.bubble_expanded_view, + this /* parent */, false /* attachToRoot */); mExpandedViewContainer.setElevation(elevation); mExpandedViewContainer.setPadding(padding, padding, padding, padding); mExpandedViewContainer.setClipChildren(false); @@ -390,16 +393,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F ExpandableNotificationRow row = mExpandedBubble.getRowView(); if (!row.equals(mExpandedViewContainer.getChildAt(0))) { // Different expanded view than what we have - mExpandedViewContainer.removeAllViews(); + mExpandedViewContainer.setExpandedView(null); } - mExpandedViewContainer.addView(row); + int pointerPosition = mExpandedBubble.getPosition().x + + (mExpandedBubble.getWidth() / 2); + mExpandedViewContainer.setPointerPosition(pointerPosition); + mExpandedViewContainer.setExpandedView(row); } } private void applyCurrentState() { mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); if (!mIsExpanded) { - mExpandedViewContainer.removeAllViews(); + mExpandedViewContainer.setExpandedView(null); } else { mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight()); ExpandableNotificationRow row = mExpandedBubble.getRowView(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java index 8262b54f3cd6..8224365e7b96 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java @@ -51,6 +51,8 @@ public class PipDismissViewController { // Used for dismissing a bubble -- bubble should be in the target to be considered a dismiss private View mTargetView; + private int mTargetSlop; + private Point mWindowSize; private int[] mLoc = new int[2]; private boolean mIntersecting; private Vibrator mVibe; @@ -69,12 +71,14 @@ public class PipDismissViewController { // Determine sizes for the view final Rect stableInsets = new Rect(); WindowManagerWrapper.getInstance().getStableInsets(stableInsets); - final Point windowSize = new Point(); - mWindowManager.getDefaultDisplay().getRealSize(windowSize); + mWindowSize = new Point(); + mWindowManager.getDefaultDisplay().getRealSize(mWindowSize); final int gradientHeight = mContext.getResources().getDimensionPixelSize( R.dimen.pip_dismiss_gradient_height); final int bottomMargin = mContext.getResources().getDimensionPixelSize( R.dimen.pip_dismiss_text_bottom_margin); + mTargetSlop = mContext.getResources().getDimensionPixelSize( + R.dimen.bubble_dismiss_slop); // Create a new view for the dismiss target LayoutInflater inflater = LayoutInflater.from(mContext); @@ -96,7 +100,7 @@ public class PipDismissViewController { // Add the target to the window LayoutParams lp = new LayoutParams( LayoutParams.MATCH_PARENT, gradientHeight, - 0, windowSize.y - gradientHeight, + 0, mWindowSize.y - gradientHeight, LayoutParams.TYPE_NAVIGATION_BAR_PANEL, LayoutParams.FLAG_LAYOUT_IN_SCREEN | LayoutParams.FLAG_NOT_TOUCHABLE @@ -112,7 +116,7 @@ public class PipDismissViewController { /** - * Updates the dismiss target based on location of the view. + * Updates the dismiss target based on location of the view, only used for bubbles not for PIP. * * @return whether the view is within the dismiss target. */ @@ -127,11 +131,13 @@ public class PipDismissViewController { mTargetView.getLocationOnScreen(mLoc); Rect targetRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + mTargetView.getWidth(), mLoc[1] + mTargetView.getHeight()); + expandRect(targetRect, mTargetSlop); boolean intersecting = targetRect.intersect(viewRect); - if (intersecting && !mIntersecting) { + if (intersecting != mIntersecting) { // TODO: is this the right effect? - mVibe.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)); - mIntersecting = true; + mVibe.vibrate(VibrationEffect.get(intersecting + ? VibrationEffect.EFFECT_CLICK + : VibrationEffect.EFFECT_TICK)); } mIntersecting = intersecting; return intersecting; @@ -139,7 +145,6 @@ public class PipDismissViewController { return false; } - /** * Shows the dismiss target. */ @@ -172,4 +177,11 @@ public class PipDismissViewController { .start(); } } + + private void expandRect(Rect outRect, int expandAmount) { + outRect.left = Math.max(0, outRect.left - expandAmount); + outRect.top = Math.max(0, outRect.top - expandAmount); + outRect.right = Math.min(mWindowSize.x, outRect.right + expandAmount); + outRect.bottom = Math.min(mWindowSize.y, outRect.bottom + expandAmount); + } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 8495fd38b5d4..86ce60d18114 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -224,6 +224,9 @@ public class PipMenuActivity extends Activity { // next tap mTouchState.scheduleDoubleTapTimeoutCallback(); } + // Fall through + case MotionEvent.ACTION_CANCEL: + mTouchState.reset(); break; } return true; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index 3a96595dee06..32fd2dcedd0e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -19,14 +19,19 @@ import android.animation.ValueAnimator; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; +import android.graphics.Path; +import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.PathShape; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.Log; +import android.util.PathParser; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -46,6 +51,7 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private static final String TAG = "QSTileBaseView"; + private static final int ICON_MASK_ID = com.android.internal.R.string.config_icon_mask; private final H mHandler = new H(); private final int[] mLocInScreen = new int[2]; private final FrameLayout mIconFrame; @@ -62,6 +68,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private final int mColorInactive; private final int mColorDisabled; private int mCircleColor; + private int mBgSize; public QSTileBaseView(Context context, QSIconView icon) { this(context, icon, false); @@ -71,15 +78,23 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { super(context); // Default to Quick Tile padding, and QSTileView will specify its own padding. int padding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding); - mIconFrame = new FrameLayout(context); mIconFrame.setForegroundGravity(Gravity.CENTER); int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); addView(mIconFrame, new LayoutParams(size, size)); mBg = new ImageView(getContext()); + Path path = new Path(PathParser.createPathFromPathData( + context.getResources().getString(ICON_MASK_ID))); + float pathSize = AdaptiveIconDrawable.MASK_SIZE; + PathShape p = new PathShape(path, pathSize, pathSize); + ShapeDrawable d = new ShapeDrawable(p); + int bgSize = context.getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size); + d.setIntrinsicHeight(bgSize); + d.setIntrinsicWidth(bgSize); mBg.setScaleType(ScaleType.FIT_CENTER); - mBg.setImageResource(R.drawable.ic_qs_circle); - mIconFrame.addView(mBg); + mBg.setImageDrawable(d); + mIconFrame.addView(mBg, ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); mIcon = icon; FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -107,7 +122,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setFocusable(true); } - public View getBgCicle() { + public View getBgCircle() { return mBg; } @@ -303,6 +318,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private class H extends Handler { private static final int STATE_CHANGED = 1; + public H() { super(Looper.getMainLooper()); } @@ -314,4 +330,4 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } } } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 451297bbd7e4..cc27135ce1e1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -17,9 +17,10 @@ package com.android.systemui.qs.tileimpl; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_CONTEXT; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_IS_FULL_QS; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_BAR_STATE; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; @@ -52,6 +53,7 @@ import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.qs.PagedTileLayout.TilePage; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QuickStatusBarHeader; +import com.android.systemui.statusbar.StatusBarStateController; import java.util.ArrayList; @@ -61,6 +63,8 @@ import java.util.ArrayList; * State management done on a looper provided by the host. Tiles should update state in * handleUpdateState. Callbacks affecting state should use refreshState to trigger another * state update pass on tile looper. + * + * @param <TState> see above */ public abstract class QSTileImpl<TState extends State> implements QSTile { protected final String TAG = "Tile." + getClass().getSimpleName(); @@ -76,6 +80,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile { protected final Handler mUiHandler = new Handler(Looper.getMainLooper()); private final ArraySet<Object> mListeners = new ArraySet<>(); private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); + private final StatusBarStateController + mStatusBarStateController = Dependency.get(StatusBarStateController.class); private final ArrayList<Callback> mCallbacks = new ArrayList<>(); private final Object mStaleListener = new Object(); @@ -172,17 +178,23 @@ public abstract class QSTileImpl<TState extends State> implements QSTile { } public void click() { - mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION))); + mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION) + .addTaggedData(FIELD_STATUS_BAR_STATE, + mStatusBarStateController.getState()))); mHandler.sendEmptyMessage(H.CLICK); } public void secondaryClick() { - mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION))); + mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION) + .addTaggedData(FIELD_STATUS_BAR_STATE, + mStatusBarStateController.getState()))); mHandler.sendEmptyMessage(H.SECONDARY_CLICK); } public void longClick() { - mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION))); + mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION) + .addTaggedData(FIELD_STATUS_BAR_STATE, + mStatusBarStateController.getState()))); mHandler.sendEmptyMessage(H.LONG_CLICK); Prefs.putInt( @@ -196,7 +208,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile { logMaker.addTaggedData(FIELD_QS_VALUE, ((BooleanState) mState).value ? 1 : 0); } return logMaker.setSubtype(getMetricsCategory()) - .addTaggedData(FIELD_CONTEXT, mIsFullQs) + .addTaggedData(FIELD_IS_FULL_QS, mIsFullQs) .addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java index 3334f8b45735..a5c0a2d3b4d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java @@ -67,7 +67,7 @@ public interface NotificationPresenter extends ExpandableNotificationRow.OnExpan /** * True if the presenter is currently locked. */ - default boolean isPresenterLocked() { return false; } + boolean isPresenterLocked(); /** * Called when the row states are updated by {@link NotificationViewHierarchyManager}. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index ba69f3bb1bc2..9391737fe23c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -218,88 +218,7 @@ public class NotificationRemoteInputManager implements Dumpable { return false; } - ViewParent p = view.getParent(); - RemoteInputView riv = null; - while (p != null) { - if (p instanceof View) { - View pv = (View) p; - if (pv.isRootNamespace()) { - riv = findRemoteInputView(pv); - break; - } - } - p = p.getParent(); - } - ExpandableNotificationRow row = null; - while (p != null) { - if (p instanceof ExpandableNotificationRow) { - row = (ExpandableNotificationRow) p; - break; - } - p = p.getParent(); - } - - if (row == null) { - return false; - } - - row.setUserExpanded(true); - - if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) { - final int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); - if (mLockscreenUserManager.isLockscreenPublicMode(userId)) { - mCallback.onLockedRemoteInput(row, view); - return true; - } - if (mUserManager.getUserInfo(userId).isManagedProfile() - && mKeyguardManager.isDeviceLocked(userId)) { - mCallback.onLockedWorkRemoteInput(userId, row, view); - return true; - } - } - - if (riv == null) { - riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild()); - if (riv == null) { - return false; - } - if (!row.getPrivateLayout().getExpandedChild().isShown()) { - mCallback.onMakeExpandedVisibleForRemoteInput(row, view); - return true; - } - } - - int width = view.getWidth(); - if (view instanceof TextView) { - // Center the reveal on the text which might be off-center from the TextView - TextView tv = (TextView) view; - if (tv.getLayout() != null) { - int innerWidth = (int) tv.getLayout().getLineWidth(0); - innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight(); - width = Math.min(width, innerWidth); - } - } - int cx = view.getLeft() + width / 2; - int cy = view.getTop() + view.getHeight() / 2; - int w = riv.getWidth(); - int h = riv.getHeight(); - int r = Math.max( - Math.max(cx + cy, cx + (h - cy)), - Math.max((w - cx) + cy, (w - cx) + (h - cy))); - - riv.setRevealParameters(cx, cy, r); - riv.setPendingIntent(pendingIntent); - riv.setRemoteInput(inputs, input); - riv.focusAnimated(); - - return true; - } - - private RemoteInputView findRemoteInputView(View v) { - if (v == null) { - return null; - } - return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG); + return activateRemoteInput(view, inputs, input, pendingIntent); } }; @@ -355,6 +274,102 @@ public class NotificationRemoteInputManager implements Dumpable { } /** + * Activates a given {@link RemoteInput} + * + * @param view The view of the action button or suggestion chip that was tapped. + * @param inputs The remote inputs that need to be sent to the app. + * @param input The remote input that needs to be activated. + * @param pendingIntent The pending intent to be sent to the app. + * @return Whether the {@link RemoteInput} was activated. + */ + public boolean activateRemoteInput(View view, RemoteInput[] inputs, RemoteInput input, + PendingIntent pendingIntent) { + + ViewParent p = view.getParent(); + RemoteInputView riv = null; + while (p != null) { + if (p instanceof View) { + View pv = (View) p; + if (pv.isRootNamespace()) { + riv = findRemoteInputView(pv); + break; + } + } + p = p.getParent(); + } + ExpandableNotificationRow row = null; + while (p != null) { + if (p instanceof ExpandableNotificationRow) { + row = (ExpandableNotificationRow) p; + break; + } + p = p.getParent(); + } + + if (row == null) { + return false; + } + + row.setUserExpanded(true); + + if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) { + final int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); + if (mLockscreenUserManager.isLockscreenPublicMode(userId)) { + mCallback.onLockedRemoteInput(row, view); + return true; + } + if (mUserManager.getUserInfo(userId).isManagedProfile() + && mKeyguardManager.isDeviceLocked(userId)) { + mCallback.onLockedWorkRemoteInput(userId, row, view); + return true; + } + } + + if (riv == null) { + riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild()); + if (riv == null) { + return false; + } + if (!row.getPrivateLayout().getExpandedChild().isShown()) { + mCallback.onMakeExpandedVisibleForRemoteInput(row, view); + return true; + } + } + + int width = view.getWidth(); + if (view instanceof TextView) { + // Center the reveal on the text which might be off-center from the TextView + TextView tv = (TextView) view; + if (tv.getLayout() != null) { + int innerWidth = (int) tv.getLayout().getLineWidth(0); + innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight(); + width = Math.min(width, innerWidth); + } + } + int cx = view.getLeft() + width / 2; + int cy = view.getTop() + view.getHeight() / 2; + int w = riv.getWidth(); + int h = riv.getHeight(); + int r = Math.max( + Math.max(cx + cy, cx + (h - cy)), + Math.max((w - cx) + cy, (w - cx) + (h - cy))); + + riv.setRevealParameters(cx, cy, r); + riv.setPendingIntent(pendingIntent); + riv.setRemoteInput(inputs, input); + riv.focusAnimated(); + + return true; + } + + private RemoteInputView findRemoteInputView(View v) { + if (v == null) { + return null; + } + return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG); + } + + /** * Adds all the notification lifetime extenders. Each extender represents a reason for the * NotificationRemoteInputManager to keep a notification lifetime extended. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java new file mode 100644 index 000000000000..13e991ba2431 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification; + +/** + * Listener interface for when NotificationEntryManager needs to tell + * NotificationGroupAlertTransferHelper things. Will eventually grow to be a general-purpose + * listening interface for the NotificationEntryManager. + */ +public interface AlertTransferListener { + /** + * Called when a new notification is posted. At this point, the notification is "pending": its + * views haven't been inflated yet and most of the system pretends like it doesn't exist yet. + */ + void onPendingEntryAdded(NotificationData.Entry entry); + + /** + * Called when an existing notification's views are reinflated (usually due to an update being + * posted to that notification). + */ + void onEntryReinflated(NotificationData.Entry entry); + + /** + * Called when a notification has been removed (either because the user swiped it away or + * because the developer retracted it). + */ + void onEntryRemoved(NotificationData.Entry entry); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java index 3f8583c6241b..f543b4622611 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java @@ -588,7 +588,7 @@ public class NotificationData { } public boolean areGutsExposed() { - return row != null && row.getGuts().isExposed(); + return row != null && row.getGuts() != null && row.getGuts().isExposed(); } public boolean isChildInGroup() { @@ -815,9 +815,18 @@ public class NotificationData { public boolean isHighPriority(StatusBarNotification statusBarNotification) { if (mRankingMap != null) { getRanking(statusBarNotification.getKey(), mTmpRanking); - return mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT + if (mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT || statusBarNotification.getNotification().isForegroundService() - || statusBarNotification.getNotification().hasMediaSession(); + || statusBarNotification.getNotification().hasMediaSession()) { + return true; + } + if (mGroupManager.isSummaryOfGroup(statusBarNotification)) { + for (Entry child : mGroupManager.getLogicalChildren(statusBarNotification)) { + if (isHighPriority(child.notification)) { + return true; + } + } + } } return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 16a3849a6eec..e1b381b5f0b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -61,7 +61,6 @@ import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.EventLogTags; import com.android.systemui.ForegroundServiceController; -import com.android.systemui.InitController; import com.android.systemui.R; import com.android.systemui.UiOffloadThread; import com.android.systemui.bubbles.BubbleController; @@ -83,7 +82,6 @@ import com.android.systemui.statusbar.notification.row.NotificationInflater; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; @@ -117,8 +115,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. private final NotificationGroupManager mGroupManager = Dependency.get(NotificationGroupManager.class); - private final NotificationGroupAlertTransferHelper mGroupAlertTransferHelper = - Dependency.get(NotificationGroupAlertTransferHelper.class); private final NotificationGutsManager mGutsManager = Dependency.get(NotificationGutsManager.class); private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); @@ -157,6 +153,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. = new ArrayList<>(); private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private NotificationViewHierarchyManager.StatusBarStateListener mStatusBarStateListener; + @Nullable private AlertTransferListener mAlertTransferListener; private final class NotificationClicker implements View.OnClickListener { @@ -258,12 +255,10 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mMessagingUtil = new NotificationMessagingUtil(context); mBubbleController.setDismissListener(this /* bubbleEventListener */); mNotificationData = new NotificationData(); - Dependency.get(InitController.class).addPostInitTask(this::onPostInit); } - private void onPostInit() { - mGroupAlertTransferHelper.setPendingEntries(mPendingNotifications); - mGroupManager.addOnGroupChangeListener(mGroupAlertTransferHelper); + public void setAlertTransferListener(AlertTransferListener listener) { + mAlertTransferListener = listener; } /** @@ -587,7 +582,9 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mVisualStabilityManager.onLowPriorityUpdated(entry); mPresenter.updateNotificationViews(); } - mGroupAlertTransferHelper.onInflationFinished(entry); + if (mAlertTransferListener != null) { + mAlertTransferListener.onEntryReinflated(entry); + } } } entry.setLowPriorityStateUpdated(false); @@ -600,8 +597,12 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. private void removeNotificationInternal(String key, @Nullable NotificationListenerService.RankingMap ranking, boolean forceRemove) { + final NotificationData.Entry entry = mNotificationData.get(key); + abortExistingInflation(key); - mGroupAlertTransferHelper.cleanUpPendingAlertInfo(key); + if (mAlertTransferListener != null) { + mAlertTransferListener.onEntryRemoved(entry); + } // Attempt to remove notifications from their alert managers (heads up, ambient pulse). // Though the remove itself may fail, it lets the manager know to remove as soon as @@ -620,8 +621,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mAmbientPulseManager.removeNotification(key, false /* ignoreEarliestRemovalTime */); } - NotificationData.Entry entry = mNotificationData.get(key); - if (entry == null) { mCallback.onNotificationRemoved(key, null /* old */); return; @@ -846,7 +845,9 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mNotificationData.getImportance(key)); mPendingNotifications.put(key, shadeEntry); - mGroupAlertTransferHelper.onPendingEntryAdded(shadeEntry); + if (mAlertTransferListener != null) { + mAlertTransferListener.onPendingEntryAdded(shadeEntry); + } } @VisibleForTesting @@ -1231,6 +1232,15 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. } /** + * @return An iterator for all "pending" notifications. Pending notifications are newly-posted + * notifications whose views have not yet been inflated. In general, the system pretends like + * these don't exist, although there are a couple exceptions. + */ + public Iterable<NotificationData.Entry> getPendingNotificationsIterator() { + return mPendingNotifications.values(); + } + + /** * Callback for NotificationEntryManager. */ public interface Callback { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 8214ea6c7616..8bed3663cf49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1098,6 +1098,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mHeadsUpManager = headsUpManager; } + public HeadsUpManager getHeadsUpManager() { + return mHeadsUpManager; + } + public void setGutsView(MenuItem item) { if (mGuts != null && item.getGutsView() instanceof NotificationGuts.GutsContent) { ((NotificationGuts.GutsContent) item.getGutsView()).setGutsParent(mGuts); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 20c48163e6b1..b1fa6a531438 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -21,7 +21,6 @@ import android.content.Context; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -60,10 +59,11 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { private ViewGroup mTransientContainer; private boolean mInShelf; private boolean mTransformingInShelf; - @Nullable private ExpandableViewState mViewState; + private final ExpandableViewState mViewState; public ExpandableView(Context context, AttributeSet attrs) { super(context, attrs); + mViewState = createExpandableViewState(); } @Override @@ -536,11 +536,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { /** Sets {@link ExpandableViewState} to default state. */ public ExpandableViewState resetViewState() { - // TODO(http://b/119762423): Move the null check to getViewState(). - if (mViewState == null) { - mViewState = createExpandableViewState(); - } - // initialize with the default values of the view mViewState.height = getIntrinsicHeight(); mViewState.gone = getVisibility() == View.GONE; @@ -573,9 +568,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { /** Applies internal {@link ExpandableViewState} to this view. */ public void applyViewState() { - if (mViewState == null) { - Log.wtf(TAG, "No child state was found when applying this state to the hostView"); - } else if (!mViewState.gone) { + if (!mViewState.gone) { mViewState.applyToView(this); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 689d6d530308..edd54ca936fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -91,6 +91,7 @@ public class NotificationContentView extends FrameLayout { private SmartReplyConstants mSmartReplyConstants; private SmartReplyView mExpandedSmartReplyView; + private SmartReplyView mHeadsUpSmartReplyView; private SmartReplyController mSmartReplyController; private NotificationViewWrapper mContractedWrapper; @@ -253,6 +254,9 @@ public class NotificationContentView extends FrameLayout { } if (mHeadsUpChild != null) { int maxHeight = mHeadsUpHeight; + if (mHeadsUpSmartReplyView != null) { + maxHeight += mHeadsUpSmartReplyView.getHeightUpperLimit(); + } maxHeight += mHeadsUpWrapper.getExtraMeasureHeight(); int size = maxHeight; ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams(); @@ -955,6 +959,9 @@ public class NotificationContentView extends FrameLayout { if (mExpandedSmartReplyView != null) { mExpandedSmartReplyView.setBackgroundTintColor(color); } + if (mHeadsUpSmartReplyView != null) { + mHeadsUpSmartReplyView.setBackgroundTintColor(color); + } } public int getVisibleType() { @@ -1472,6 +1479,10 @@ public class NotificationContentView extends FrameLayout { entry, smartRepliesAndActions.smartReplies.choices.length); } } + if (mHeadsUpChild != null) { + mHeadsUpSmartReplyView = + applySmartReplyView(mHeadsUpChild, smartRepliesAndActions, entry); + } } private SmartReplyView applySmartReplyView(View view, @@ -1520,7 +1531,8 @@ public class NotificationContentView extends FrameLayout { } if (smartRepliesAndActions.smartActions != null) { smartReplyView.addSmartActions( - smartRepliesAndActions.smartActions, mSmartReplyController, entry); + smartRepliesAndActions.smartActions, mSmartReplyController, entry, + mContainingNotification.getHeadsUpManager()); } smartReplyContainer.setVisibility(View.VISIBLE); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index 50564e386e4b..d97162c2ef1e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.service.notification.StatusBarNotification; +import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -47,6 +48,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import java.util.ArrayList; import java.util.List; +import java.util.Map; public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnClickListener, ExpandableNotificationRow.LayoutListener { @@ -74,6 +76,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private MenuItem mSnoozeItem; private ArrayList<MenuItem> mLeftMenuItems; private ArrayList<MenuItem> mRightMenuItems; + private final Map<View, MenuItem> mMenuItemsByView = new ArrayMap<>(); private OnMenuEventListener mMenuListener; private ValueAnimator mFadeAnimator; @@ -287,6 +290,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl private void populateMenuViews() { if (mMenuContainer != null) { mMenuContainer.removeAllViews(); + mMenuItemsByView.clear(); } else { mMenuContainer = new FrameLayout(mContext); } @@ -486,10 +490,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl final int centerY = v.getHeight() / 2; final int x = mIconLocation[0] - mParentLocation[0] + centerX; final int y = mIconLocation[1] - mParentLocation[1] + centerY; - final int index = mMenuContainer.indexOfChild(v); - if (mMenuListener != null) { - mMenuListener.onMenuClicked(mParent, x, y, - (mOnLeft ? mLeftMenuItems : mRightMenuItems).get(index)); + if (mMenuItemsByView.containsKey(v)) { + mMenuListener.onMenuClicked(mParent, x, y, mMenuItemsByView.get(v)); } } @@ -641,8 +643,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate( R.layout.notification_info, null, false); int iconResId = isCurrentlySilent - ? R.drawable.ic_notifications_alert - : R.drawable.ic_notifications_silence; + ? R.drawable.ic_notifications_silence + : R.drawable.ic_notifications_alert; return new NotificationMenuItem(context, infoDescription, infoContent, iconResId); } @@ -665,6 +667,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl lp.height = mHorizSpaceForIcon; menuView.setLayoutParams(lp); } + mMenuItemsByView.put(menuView, item); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java index b83ebc7f8ea1..9c8b1b1e5227 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java @@ -56,6 +56,11 @@ public class NavigationBackAction extends NavigationGestureAction { } @Override + public boolean allowHitTargetToMoveOverDrag() { + return true; + } + + @Override public boolean canPerformAction() { return mProxySender.getBackButtonAlpha() > 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index cd6e1d794428..30e840926698 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -63,6 +63,7 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.DockedStackExistsListener; import com.android.systemui.Interpolators; @@ -96,7 +97,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav final static boolean ALTERNATE_CAR_MODE_UI = false; - final Display mDisplay; View mCurrentView = null; View[] mRotatedViews = new View[4]; @@ -154,6 +154,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private QuickScrubAction mQuickScrubAction; private QuickStepAction mQuickStepAction; private NavigationBackAction mBackAction; + private QuickSwitchAction mQuickSwitchAction; /** * Helper that is responsible for showing the right toast when a disallowed activity operation @@ -212,8 +213,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private final OnClickListener mImeSwitcherClickListener = new OnClickListener() { @Override public void onClick(View view) { - mContext.getSystemService(InputMethodManager.class) - .showInputMethodPicker(true /* showAuxiliarySubtypes */); + mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem( + true /* showAuxiliarySubtypes */, getContext().getDisplayId()); } }; @@ -281,8 +282,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav public NavigationBarView(Context context, AttributeSet attrs) { super(context, attrs); - mDisplay = context.getDisplay(); - mVertical = false; mLongClickableAccessibilityButton = false; @@ -326,9 +325,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mQuickScrubAction = new QuickScrubAction(this, mOverviewProxyService); mQuickStepAction = new QuickStepAction(this, mOverviewProxyService); mBackAction = new NavigationBackAction(this, mOverviewProxyService); + mQuickSwitchAction = new QuickSwitchAction(this, mOverviewProxyService); mDefaultGestureMap = new NavigationGestureAction[] { mQuickStepAction, null /* swipeDownAction*/, null /* swipeLeftAction */, - mQuickScrubAction + mQuickScrubAction, null /* swipeLeftEdgeAction */, null /* swipeRightEdgeAction */ }; mPrototypeController = new NavigationPrototypeController(mHandler, mContext); @@ -359,7 +359,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav getNavigationActionFromType(assignedMap[0], mDefaultGestureMap[0]), getNavigationActionFromType(assignedMap[1], mDefaultGestureMap[1]), getNavigationActionFromType(assignedMap[2], mDefaultGestureMap[2]), - getNavigationActionFromType(assignedMap[3], mDefaultGestureMap[3])); + getNavigationActionFromType(assignedMap[3], mDefaultGestureMap[3]), + getNavigationActionFromType(assignedMap[4], mDefaultGestureMap[4]), + getNavigationActionFromType(assignedMap[5], mDefaultGestureMap[5])); } } @@ -372,6 +374,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return mQuickScrubAction; case NavigationPrototypeController.ACTION_BACK: return mBackAction; + case NavigationPrototypeController.ACTION_QUICKSWITCH: + return mQuickSwitchAction; default: return defaultAction; } @@ -652,8 +656,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav Log.i(TAG, "updateNavButtonIcons (b/113914868): home disabled=" + disableHome + " mDisabledFlags=" + mDisabledFlags); - // Always disable recents when alternate car mode UI is active. - boolean disableRecent = mUseCarModeUi || !isOverviewEnabled(); + // Always disable recents when alternate car mode UI is active and for secondary displays. + boolean disableRecent = isRecentsButtonDisabled(); boolean disableBack = QuickStepController.shouldhideBackButton(getContext()) || (((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) && !useAltBack); @@ -689,6 +693,16 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); } + @VisibleForTesting + boolean isRecentsButtonDisabled() { + return mUseCarModeUi || !isOverviewEnabled() + || getContext().getDisplayId() != Display.DEFAULT_DISPLAY; + } + + private Display getContextDisplay() { + return getContext().getDisplay(); + } + public boolean inScreenPinning() { return ActivityManagerWrapper.getInstance().isScreenPinningActive(); } @@ -890,7 +904,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } private void updateCurrentView() { - final int rot = mDisplay.getRotation(); + final int rot = getContextDisplay().getRotation(); for (int i=0; i<4; i++) { mRotatedViews[i].setVisibility(View.GONE); } @@ -954,7 +968,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav int navBarPos = NAV_BAR_INVALID; try { navBarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition( - mDisplay.getDisplayId()); + getContext().getDisplayId()); } catch (RemoteException e) { Slog.e(TAG, "Failed to get nav bar position.", e); } @@ -1128,7 +1142,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav pw.println("NavigationBarView {"); final Rect r = new Rect(); final Point size = new Point(); - mDisplay.getRealSize(size); + getContextDisplay().getRealSize(size); pw.println(String.format(" this: " + StatusBar.viewInfo(this) + " " + visibilityToString(getVisibility()))); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java index a8d00c454548..8c57fc31c3b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java @@ -112,7 +112,7 @@ public abstract class NavigationGestureAction { /** * @return whether or not to move the button that started gesture over with user input drag */ - public boolean requiresDragWithHitTarget() { + public boolean allowHitTargetToMoveOverDrag() { return false; } @@ -139,9 +139,9 @@ public abstract class NavigationGestureAction { * Tell if action is enabled. Compared to {@link #canPerformAction()} this is based on settings * if the action is disabled for a particular gesture. For example a back action can be enabled * however if there is nothing to back to then {@link #canPerformAction()} should return false. - * In this way if the action requires {@link #requiresDragWithHitTarget()} then if enabled, the - * button can be dragged with a large dampening factor during the gesture but will not activate - * the action. + * In this way if the action requires {@link #allowHitTargetToMoveOverDrag()} then if enabled, + * the button can be dragged with a large dampening factor during the gesture but will not + * activate the action. * @return true if this action is enabled and can run */ public abstract boolean isEnabled(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java index e8c0bf13644f..b11b6d472713 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java @@ -24,7 +24,6 @@ import android.os.Handler; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; -import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -46,6 +45,7 @@ public class NavigationPrototypeController extends ContentObserver { static final int ACTION_QUICKSTEP = 1; static final int ACTION_QUICKSCRUB = 2; static final int ACTION_BACK = 3; + static final int ACTION_QUICKSWITCH = 4; private OnPrototypeChangedListener mListener; @@ -53,7 +53,7 @@ public class NavigationPrototypeController extends ContentObserver { * Each index corresponds to a different action set in QuickStepController * {@see updateSwipeLTRBackSetting} */ - private int[] mActionMap = new int[4]; + private int[] mActionMap = new int[6]; private final Context mContext; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 2a68fa598603..dd81c4e20d6d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -29,7 +29,9 @@ import com.android.systemui.statusbar.AmbientPulseManager.OnAmbientChangedListen import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateController.StateListener; +import com.android.systemui.statusbar.notification.AlertTransferListener; import com.android.systemui.statusbar.notification.NotificationData.Entry; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.row.NotificationInflater.AsyncInflationTask; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup; @@ -38,8 +40,6 @@ import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; import java.util.Objects; /** @@ -47,8 +47,8 @@ import java.util.Objects; * {@link HeadsUpManager}, {@link AmbientPulseManager}. In particular, this class deals with keeping * the correct notification in a group alerting based off the group suppression. */ -public class NotificationGroupAlertTransferHelper implements OnGroupChangeListener, - OnHeadsUpChangedListener, OnAmbientChangedListener, StateListener { +public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener, + OnAmbientChangedListener, StateListener { private static final long ALERT_TRANSFER_TIMEOUT = 300; @@ -69,15 +69,7 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen private final NotificationGroupManager mGroupManager = Dependency.get(NotificationGroupManager.class); - // TODO(b/119637830): It would be good if GroupManager already had all pending notifications as - // normal children (i.e. add notifications to GroupManager before inflation) so that we don't - // have to have this dependency. We'd also have to worry less about the suppression not being up - // to date. - /** - * Notifications that are currently inflating for the first time. Used to remove an incorrectly - * alerting notification faster. - */ - private HashMap<String, Entry> mPendingNotifications; + private NotificationEntryManager mEntryManager; private boolean mIsDozing; @@ -85,6 +77,23 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen Dependency.get(StatusBarStateController.class).addCallback(this); } + /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */ + public void bind(NotificationEntryManager entryManager, + NotificationGroupManager groupManager) { + if (mEntryManager != null) { + throw new IllegalStateException("Already bound."); + } + + // TODO(b/119637830): It would be good if GroupManager already had all pending notifications + // as normal children (i.e. add notifications to GroupManager before inflation) so that we + // don't have to have this dependency. We'd also have to worry less about the suppression + // not being up to date. + mEntryManager = entryManager; + + mEntryManager.setAlertTransferListener(mAlertTransferListener); + groupManager.addOnGroupChangeListener(mOnGroupChangeListener); + } + /** * Whether or not a notification has transferred its alert state to the notification and * the notification should alert after inflating. @@ -97,25 +106,10 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen return alertInfo != null && alertInfo.isStillValid(); } - /** - * Removes any alerts pending on this entry. Note that this will not stop any inflation tasks - * started by a transfer, so this should only be used as clean-up for when inflation is stopped - * and the pending alert no longer needs to happen. - * - * @param key notification key that may have info that needs to be cleaned up - */ - public void cleanUpPendingAlertInfo(@NonNull String key) { - mPendingAlerts.remove(key); - } - public void setHeadsUpManager(HeadsUpManager headsUpManager) { mHeadsUpManager = headsUpManager; } - public void setPendingEntries(HashMap<String, Entry> pendingNotifications) { - mPendingNotifications = pendingNotifications; - } - @Override public void onStateChanged(int newState) {} @@ -130,43 +124,45 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen mIsDozing = isDozing; } - @Override - public void onGroupCreated(NotificationGroup group, String groupKey) { - mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group)); - } + private final OnGroupChangeListener mOnGroupChangeListener = new OnGroupChangeListener() { + @Override + public void onGroupCreated(NotificationGroup group, String groupKey) { + mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group)); + } - @Override - public void onGroupRemoved(NotificationGroup group, String groupKey) { - mGroupAlertEntries.remove(groupKey); - } + @Override + public void onGroupRemoved(NotificationGroup group, String groupKey) { + mGroupAlertEntries.remove(groupKey); + } - @Override - public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) { - AlertingNotificationManager alertManager = getActiveAlertManager(); - if (suppressed) { - if (alertManager.isAlerting(group.summary.key)) { - handleSuppressedSummaryAlerted(group.summary, alertManager); - } - } else { - // Group summary can be null if we are no longer suppressed because the summary was - // removed. In that case, we don't need to alert the summary. - if (group.summary == null) { - return; - } - GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey( - group.summary.notification)); - // Group is no longer suppressed. We should check if we need to transfer the alert - // back to the summary now that it's no longer suppressed. - if (groupAlertEntry.mAlertSummaryOnNextAddition) { - if (!alertManager.isAlerting(group.summary.key)) { - alertNotificationWhenPossible(group.summary, alertManager); + @Override + public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) { + AlertingNotificationManager alertManager = getActiveAlertManager(); + if (suppressed) { + if (alertManager.isAlerting(group.summary.key)) { + handleSuppressedSummaryAlerted(group.summary, alertManager); } - groupAlertEntry.mAlertSummaryOnNextAddition = false; } else { - checkShouldTransferBack(groupAlertEntry); + // Group summary can be null if we are no longer suppressed because the summary was + // removed. In that case, we don't need to alert the summary. + if (group.summary == null) { + return; + } + GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey( + group.summary.notification)); + // Group is no longer suppressed. We should check if we need to transfer the alert + // back to the summary now that it's no longer suppressed. + if (groupAlertEntry.mAlertSummaryOnNextAddition) { + if (!alertManager.isAlerting(group.summary.key)) { + alertNotificationWhenPossible(group.summary, alertManager); + } + groupAlertEntry.mAlertSummaryOnNextAddition = false; + } else { + checkShouldTransferBack(groupAlertEntry); + } } } - } + }; @Override public void onAmbientStateChanged(Entry entry, boolean isAmbient) { @@ -185,37 +181,42 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen } } - /** - * Called when the entry's reinflation has finished. If there is an alert pending, we then - * show the alert. - * - * @param entry entry whose inflation has finished - */ - public void onInflationFinished(@NonNull Entry entry) { - PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.key); - if (alertInfo != null) { - if (alertInfo.isStillValid()) { - alertNotificationWhenPossible(entry, getActiveAlertManager()); - } else { - // The transfer is no longer valid. Free the content. - entry.getRow().freeContentViewWhenSafe(alertInfo.mAlertManager.getContentFlag()); + private final AlertTransferListener mAlertTransferListener = new AlertTransferListener() { + // Called when a new notification has been posted but is not inflated yet. We use this to + // see as early as we can if we need to abort a transfer. + @Override + public void onPendingEntryAdded(Entry entry) { + String groupKey = mGroupManager.getGroupKey(entry.notification); + GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey); + if (groupAlertEntry != null) { + checkShouldTransferBack(groupAlertEntry); } } - } - /** - * Called when a new notification has been posted but is not inflated yet. We use this to see - * as early as we can if we need to abort a transfer. - * - * @param entry entry that has been added - */ - public void onPendingEntryAdded(@NonNull Entry entry) { - String groupKey = mGroupManager.getGroupKey(entry.notification); - GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey); - if (groupAlertEntry != null) { - checkShouldTransferBack(groupAlertEntry); + // Called when the entry's reinflation has finished. If there is an alert pending, we + // then show the alert. + @Override + public void onEntryReinflated(Entry entry) { + PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.key); + if (alertInfo != null) { + if (alertInfo.isStillValid()) { + alertNotificationWhenPossible(entry, getActiveAlertManager()); + } else { + // The transfer is no longer valid. Free the content. + entry.getRow().freeContentViewWhenSafe( + alertInfo.mAlertManager.getContentFlag()); + } + } } - } + + @Override + public void onEntryRemoved(Entry entry) { + // Removes any alerts pending on this entry. Note that this will not stop any inflation + // tasks started by a transfer, so this should only be used as clean-up for when + // inflation is stopped and the pending alert no longer needs to happen. + mPendingAlerts.remove(entry.key); + } + }; /** * Gets the number of new notifications pending inflation that will be added to the group @@ -225,11 +226,11 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen * @return the number of new notifications that will be added to the group */ private int getPendingChildrenNotAlerting(@NonNull NotificationGroup group) { - if (mPendingNotifications == null) { + if (mEntryManager == null) { return 0; } int number = 0; - Collection<Entry> values = mPendingNotifications.values(); + Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator(); for (Entry entry : values) { if (isPendingNotificationInGroup(entry, group) && onlySummaryAlerts(entry)) { number++; @@ -245,10 +246,10 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen * @return true if a pending notification will add to this group */ private boolean pendingInflationsWillAddChildren(@NonNull NotificationGroup group) { - if (mPendingNotifications == null) { + if (mEntryManager == null) { return false; } - Collection<Entry> values = mPendingNotifications.values(); + Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator(); for (Entry entry : values) { if (isPendingNotificationInGroup(entry, group)) { return true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java index 2b202eb83431..bbfd51a5bfe6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java @@ -18,8 +18,6 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.Interpolators.ALPHA_IN; import static com.android.systemui.Interpolators.ALPHA_OUT; -import static com.android.systemui.recents.OverviewProxyService.DEBUG_OVERVIEW_PROXY; -import static com.android.systemui.recents.OverviewProxyService.TAG_OPS; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -31,11 +29,8 @@ import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RadialGradient; -import android.graphics.Rect; import android.graphics.Shader; -import android.os.RemoteException; import android.util.FloatProperty; -import android.util.Log; import android.view.MotionEvent; import android.view.View; @@ -46,7 +41,7 @@ import com.android.systemui.shared.recents.utilities.Utilities; /** * QuickScrub action to send to launcher to start quickscrub gesture */ -public class QuickScrubAction extends NavigationGestureAction { +public class QuickScrubAction extends QuickSwitchAction { private static final String TAG = "QuickScrubAction"; private static final float TRACK_SCALE = 0.95f; @@ -65,7 +60,6 @@ public class QuickScrubAction extends NavigationGestureAction { private final int mTrackThickness; private final int mTrackEndPadding; private final Paint mTrackPaint = new Paint(); - private final Rect mTrackRect = new Rect(); private final FloatProperty<QuickScrubAction> mTrackAlphaProperty = new FloatProperty<QuickScrubAction>("TrackAlpha") { @@ -177,7 +171,7 @@ public class QuickScrubAction extends NavigationGestureAction { x1 = mNavigationBarView.getPaddingStart() + mTrackEndPadding; x2 = x1 + width - 2 * mTrackEndPadding; } - mTrackRect.set(x1, y1, x2, y2); + mDragOverRect.set(x1, y1, x2, y2); } @Override @@ -194,15 +188,16 @@ public class QuickScrubAction extends NavigationGestureAction { mTrackPaint.setAlpha(Math.round(255f * mTrackAlpha)); // Scale the track, but apply the inverse scale from the nav bar - final float radius = mTrackRect.height() / 2; + final float radius = mDragOverRect.height() / 2; canvas.save(); - float translate = Utilities.clamp(mHighlightCenter, mTrackRect.left, mTrackRect.right); + float translate = Utilities.clamp(mHighlightCenter, mDragOverRect.left, + mDragOverRect.right); canvas.translate(translate, 0); canvas.scale(mTrackScale / mNavigationBarView.getScaleX(), 1f / mNavigationBarView.getScaleY(), - mTrackRect.centerX(), mTrackRect.centerY()); - canvas.drawRoundRect(mTrackRect.left - translate, mTrackRect.top, - mTrackRect.right - translate, mTrackRect.bottom, radius, radius, mTrackPaint); + mDragOverRect.centerX(), mDragOverRect.centerY()); + canvas.drawRoundRect(mDragOverRect.left - translate, mDragOverRect.top, + mDragOverRect.right - translate, mDragOverRect.bottom, radius, radius, mTrackPaint); canvas.restore(); } @@ -212,11 +207,6 @@ public class QuickScrubAction extends NavigationGestureAction { } @Override - public boolean disableProxyEvents() { - return true; - } - - @Override protected void onGestureStart(MotionEvent event) { updateHighlight(); ObjectAnimator trackAnimator = ObjectAnimator.ofPropertyValuesHolder(this, @@ -231,42 +221,12 @@ public class QuickScrubAction extends NavigationGestureAction { mTrackAnimator.playTogether(trackAnimator, navBarAnimator); mTrackAnimator.start(); - // Disable slippery for quick scrub to not cancel outside the nav bar - mNavigationBarView.updateSlippery(); - - try { - mProxySender.getProxy().onQuickScrubStart(); - if (DEBUG_OVERVIEW_PROXY) { - Log.d(TAG_OPS, "Quick Scrub Start"); - } - } catch (RemoteException e) { - Log.e(TAG, "Failed to send start of quick scrub.", e); - } - mProxySender.notifyQuickScrubStarted(); + startQuickGesture(event); } @Override public void onGestureMove(int x, int y) { - int trackSize, offset; - if (isNavBarVertical()) { - trackSize = mTrackRect.height(); - offset = y - mTrackRect.top; - } else { - offset = x - mTrackRect.left; - trackSize = mTrackRect.width(); - } - if (!mDragHorizontalPositive || !mDragVerticalPositive) { - offset -= isNavBarVertical() ? mTrackRect.height() : mTrackRect.width(); - } - float scrubFraction = Utilities.clamp(Math.abs(offset) * 1f / trackSize, 0, 1); - try { - mProxySender.getProxy().onQuickScrubProgress(scrubFraction); - if (DEBUG_OVERVIEW_PROXY) { - Log.d(TAG_OPS, "Quick Scrub Progress:" + scrubFraction); - } - } catch (RemoteException e) { - Log.e(TAG, "Failed to send progress of quick scrub.", e); - } + super.onGestureMove(x, y); mHighlightCenter = x; mNavigationBarView.invalidate(); } @@ -278,14 +238,7 @@ public class QuickScrubAction extends NavigationGestureAction { private void endQuickScrub(boolean animate) { animateEnd(); - try { - mProxySender.getProxy().onQuickScrubEnd(); - if (DEBUG_OVERVIEW_PROXY) { - Log.d(TAG_OPS, "Quick Scrub End"); - } - } catch (RemoteException e) { - Log.e(TAG, "Failed to send end of quick scrub.", e); - } + endQuickGesture(animate); if (!animate) { if (mTrackAnimator != null) { mTrackAnimator.end(); @@ -295,7 +248,7 @@ public class QuickScrubAction extends NavigationGestureAction { } private void updateHighlight() { - if (mTrackRect.isEmpty()) { + if (mDragOverRect.isEmpty()) { return; } int colorBase, colorGrad; @@ -306,8 +259,8 @@ public class QuickScrubAction extends NavigationGestureAction { colorBase = getContext().getColor(R.color.quick_step_track_background_background_light); colorGrad = getContext().getColor(R.color.quick_step_track_background_foreground_light); } - final RadialGradient mHighlight = new RadialGradient(0, mTrackRect.height() / 2, - mTrackRect.width() * GRADIENT_WIDTH, colorGrad, colorBase, + final RadialGradient mHighlight = new RadialGradient(0, mDragOverRect.height() / 2, + mDragOverRect.width() * GRADIENT_WIDTH, colorGrad, colorBase, Shader.TileMode.CLAMP); mTrackPaint.setShader(mHighlight); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java index 4983618ba414..0cec6371d813 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java @@ -76,7 +76,9 @@ public class QuickStepController implements GestureHelper { private static final int ACTION_SWIPE_DOWN_INDEX = 1; private static final int ACTION_SWIPE_LEFT_INDEX = 2; private static final int ACTION_SWIPE_RIGHT_INDEX = 3; - private static final int MAX_GESTURES = 4; + private static final int ACTION_SWIPE_LEFT_FROM_EDGE_INDEX = 4; + private static final int ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX = 5; + private static final int MAX_GESTURES = 6; private NavigationBarView mNavigationBarView; @@ -97,6 +99,7 @@ public class QuickStepController implements GestureHelper { private float mMaxDragLimit; private float mMinDragLimit; private float mDragDampeningFactor; + private float mEdgeSwipeThreshold; private NavigationGestureAction mCurrentAction; private NavigationGestureAction[] mGestureActions = new NavigationGestureAction[MAX_GESTURES]; @@ -128,15 +131,21 @@ public class QuickStepController implements GestureHelper { * @param swipeDownAction action after swiping down * @param swipeLeftAction action after swiping left * @param swipeRightAction action after swiping right + * @param swipeLeftFromEdgeAction action swiping left starting from the right side + * @param swipeRightFromEdgeAction action swiping right starting from the left side */ public void setGestureActions(@Nullable NavigationGestureAction swipeUpAction, @Nullable NavigationGestureAction swipeDownAction, @Nullable NavigationGestureAction swipeLeftAction, - @Nullable NavigationGestureAction swipeRightAction) { + @Nullable NavigationGestureAction swipeRightAction, + @Nullable NavigationGestureAction swipeLeftFromEdgeAction, + @Nullable NavigationGestureAction swipeRightFromEdgeAction) { mGestureActions[ACTION_SWIPE_UP_INDEX] = swipeUpAction; mGestureActions[ACTION_SWIPE_DOWN_INDEX] = swipeDownAction; mGestureActions[ACTION_SWIPE_LEFT_INDEX] = swipeLeftAction; mGestureActions[ACTION_SWIPE_RIGHT_INDEX] = swipeRightAction; + mGestureActions[ACTION_SWIPE_LEFT_FROM_EDGE_INDEX] = swipeLeftFromEdgeAction; + mGestureActions[ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX] = swipeRightFromEdgeAction; // Set the current state to all actions for (NavigationGestureAction action: mGestureActions) { @@ -233,6 +242,8 @@ public class QuickStepController implements GestureHelper { mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix); mAllowGestureDetection = true; mNotificationsVisibleOnDown = !mNavigationBarView.isNotificationsFullyCollapsed(); + mEdgeSwipeThreshold = mContext.getResources() + .getDimensionPixelSize(R.dimen.navigation_bar_edge_swipe_threshold); break; } case MotionEvent.ACTION_MOVE: { @@ -284,13 +295,19 @@ public class QuickStepController implements GestureHelper { } } else if (exceededSwipeHorizontalTouchSlop) { if (mDragHPositive ? (posH < touchDownH) : (posH > touchDownH)) { - // Swiping left (ltr) gesture - tryToStartGesture(mGestureActions[ACTION_SWIPE_LEFT_INDEX], - true /* alignedWithNavBar */, event); + // Swiping left (rtl) gesture + int index = mGestureActions[ACTION_SWIPE_LEFT_FROM_EDGE_INDEX] != null + && isEdgeSwipeAlongNavBar(touchDownH, !mDragHPositive) + ? ACTION_SWIPE_LEFT_FROM_EDGE_INDEX : ACTION_SWIPE_LEFT_INDEX; + tryToStartGesture(mGestureActions[index], true /* alignedWithNavBar */, + event); } else { // Swiping right (ltr) gesture - tryToStartGesture(mGestureActions[ACTION_SWIPE_RIGHT_INDEX], - true /* alignedWithNavBar */, event); + int index = mGestureActions[ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX] != null + && isEdgeSwipeAlongNavBar(touchDownH, mDragHPositive) + ? ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX : ACTION_SWIPE_RIGHT_INDEX; + tryToStartGesture(mGestureActions[index], true /* alignedWithNavBar */, + event); } } } @@ -333,6 +350,17 @@ public class QuickStepController implements GestureHelper { return mCurrentAction != null || deadZoneConsumed; } + private boolean isEdgeSwipeAlongNavBar(int touchDown, boolean dragPositiveDirection) { + // Detect edge swipe from side of 0 -> threshold + if (dragPositiveDirection) { + return touchDown < mEdgeSwipeThreshold; + } + // Detect edge swipe from side of size -> (size - threshold) + final int largeSide = isNavBarVertical() + ? mNavigationBarView.getHeight() : mNavigationBarView.getWidth(); + return touchDown > largeSide - mEdgeSwipeThreshold; + } + private void handleDragHitTarget(int position, int touchDown) { // Drag the hit target if gesture action requires it if (mHitTarget != null && (mGestureVerticalDragsButton || mGestureHorizontalDragsButton)) { @@ -480,7 +508,7 @@ public class QuickStepController implements GestureHelper { event.transform(mTransformLocalMatrix); // Calculate the bounding limits of drag to avoid dragging off nav bar's window - if (action.requiresDragWithHitTarget() && mHitTarget != null) { + if (action.allowHitTargetToMoveOverDrag() && mHitTarget != null) { final int[] buttonCenter = new int[2]; View button = mHitTarget.getCurrentView(); button.getLocationInWindow(buttonCenter); @@ -505,7 +533,7 @@ public class QuickStepController implements GestureHelper { // Handle direction of the hit target drag from the axis that started the gesture // Also calculate the dampening factor, weaker dampening if there is an active action - if (action.requiresDragWithHitTarget()) { + if (action.allowHitTargetToMoveOverDrag()) { if (alignedWithNavBar) { mGestureHorizontalDragsButton = true; mGestureVerticalDragsButton = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSwitchAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSwitchAction.java new file mode 100644 index 000000000000..40f2392d2610 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSwitchAction.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import static com.android.systemui.recents.OverviewProxyService.DEBUG_OVERVIEW_PROXY; +import static com.android.systemui.recents.OverviewProxyService.TAG_OPS; + +import android.annotation.NonNull; +import android.graphics.Rect; +import android.os.RemoteException; +import android.provider.Settings; +import android.util.Log; +import android.view.MotionEvent; + +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.shared.recents.utilities.Utilities; + +/** + * QuickSwitch action to send to launcher + */ +public class QuickSwitchAction extends NavigationGestureAction { + private static final String TAG = "QuickSwitchAction"; + private static final String QUICKSWITCH_ENABLED_SETTING = "QUICK_SWITCH"; + + protected final Rect mDragOverRect = new Rect(); + + public QuickSwitchAction(@NonNull NavigationBarView navigationBar, + @NonNull OverviewProxyService service) { + super(navigationBar, service); + } + + @Override + public void setBarState(boolean changed, int navBarPos, boolean dragHorPositive, + boolean dragVerPositive) { + super.setBarState(changed, navBarPos, dragHorPositive, dragVerPositive); + if (changed && isActive()) { + // End quickscrub if the state changes mid-transition + endQuickGesture(false /* animate */); + } + } + + @Override + public boolean isEnabled() { + return mNavigationBarView.isQuickScrubEnabled(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mDragOverRect.set(top, left, right, bottom); + } + + @Override + public boolean disableProxyEvents() { + return true; + } + + @Override + protected void onGestureStart(MotionEvent event) { + // Temporarily enable launcher to allow quick switch instead of quick scrub + Settings.Global.putInt(mNavigationBarView.getContext().getContentResolver(), + QUICKSWITCH_ENABLED_SETTING, 1 /* enabled */); + + startQuickGesture(event); + } + + @Override + public void onGestureMove(int x, int y) { + int dragLength, offset; + if (isNavBarVertical()) { + dragLength = mDragOverRect.height(); + offset = y - mDragOverRect.top; + } else { + offset = x - mDragOverRect.left; + dragLength = mDragOverRect.width(); + } + if (!mDragHorizontalPositive || !mDragVerticalPositive) { + offset -= dragLength; + } + float scrubFraction = Utilities.clamp(Math.abs(offset) * 1f / dragLength, 0, 1); + try { + mProxySender.getProxy().onQuickScrubProgress(scrubFraction); + if (DEBUG_OVERVIEW_PROXY) { + Log.d(TAG_OPS, "Quick Switch Progress:" + scrubFraction); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to send progress of quick switch.", e); + } + } + + @Override + protected void onGestureEnd() { + endQuickGesture(true /* animate */); + + // Disable launcher to use quick switch instead of quick scrub + Settings.Global.putInt(mNavigationBarView.getContext().getContentResolver(), + QUICKSWITCH_ENABLED_SETTING, 0 /* disabled */); + } + + protected void startQuickGesture(MotionEvent event) { + // Disable slippery for quick scrub to not cancel outside the nav bar + mNavigationBarView.updateSlippery(); + + try { + mProxySender.getProxy().onQuickScrubStart(); + if (DEBUG_OVERVIEW_PROXY) { + Log.d(TAG_OPS, "Quick Scrub Start"); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to send start of quick scrub.", e); + } + mProxySender.notifyQuickScrubStarted(); + } + + protected void endQuickGesture(boolean animate) { + try { + mProxySender.getProxy().onQuickScrubEnd(); + if (DEBUG_OVERVIEW_PROXY) { + Log.d(TAG_OPS, "Quick Scrub End"); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to send end of quick scrub.", e); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index bf53b7720e44..a508f1bdbd34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -121,8 +121,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.StatusBarIcon; -import com.android.internal.widget.MessagingGroup; -import com.android.internal.widget.MessagingMessage; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.ViewMediatorCallback; @@ -630,6 +628,8 @@ public class StatusBar extends SystemUI implements DemoMode, mBubbleController = Dependency.get(BubbleController.class); mBubbleController.setExpandListener(mBubbleExpandListener); + mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager); + mColorExtractor.addOnColorsChangedListener(this); mStatusBarStateController.addCallback(this, StatusBarStateController.RANK_STATUS_BAR); @@ -1023,7 +1023,7 @@ public class StatusBar extends SystemUI implements DemoMode, mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel, mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController, - mScrimController, mActivityLaunchAnimator); + mScrimController, mActivityLaunchAnimator, mStatusBarKeyguardViewManager); mAppOpsController.addCallback(APP_OPS, this); mNotificationListener.setUpWithPresenter(mPresenter); @@ -1116,8 +1116,6 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onDensityOrFontScaleChanged() { - MessagingMessage.dropCache(); - MessagingGroup.dropCache(); // TODO: Remove this. if (mBrightnessMirrorController != null) { mBrightnessMirrorController.onDensityOrFontScaleChanged(); 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 3550bd627377..acdd5e995322 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -37,6 +37,8 @@ import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.widget.MessagingGroup; +import com.android.internal.widget.MessagingMessage; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dependency; import com.android.systemui.InitController; @@ -62,9 +64,11 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardMonitor; -public class StatusBarNotificationPresenter implements NotificationPresenter { +public class StatusBarNotificationPresenter implements NotificationPresenter, + ConfigurationController.ConfigurationListener { private final LockscreenGestureLogger mLockscreenGestureLogger = Dependency.get(LockscreenGestureLogger.class); @@ -97,6 +101,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter { private final AccessibilityManager mAccessibilityManager; private final KeyguardManager mKeyguardManager; private final ActivityLaunchAnimator mActivityLaunchAnimator; + private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final int mMaxAllowedKeyguardNotifications; private final IStatusBarService mBarService; private boolean mReinflateNotificationsOnUserSwitched; @@ -113,13 +118,15 @@ public class StatusBarNotificationPresenter implements NotificationPresenter { ViewGroup stackScroller, DozeScrimController dozeScrimController, ScrimController scrimController, - ActivityLaunchAnimator activityLaunchAnimator) { + ActivityLaunchAnimator activityLaunchAnimator, + StatusBarKeyguardViewManager statusBarKeyguardViewManager) { mContext = context; mNotificationPanel = panel; mHeadsUpManager = headsUp; mCommandQueue = getComponent(context, CommandQueue.class); mAboveShelfObserver = new AboveShelfObserver(stackScroller); mActivityLaunchAnimator = activityLaunchAnimator; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mAboveShelfObserver.setListener(statusBarWindow.findViewById( R.id.notification_container_parent)); mAccessibilityManager = context.getSystemService(AccessibilityManager.class); @@ -165,9 +172,13 @@ public class StatusBarNotificationPresenter implements NotificationPresenter { onUserSwitched(mLockscreenUserManager.getCurrentUserId()); }); + Dependency.get(ConfigurationController.class).addCallback(this); } + @Override public void onDensityOrFontScaleChanged() { + MessagingMessage.dropCache(); + MessagingGroup.dropCache(); if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) { mEntryManager.updateNotificationsOnDensityOrFontScaleChanged(); } else { @@ -367,6 +378,12 @@ public class StatusBarNotificationPresenter implements NotificationPresenter { return mVrMode; } + @Override + public boolean isPresenterLocked() { + return mStatusBarKeyguardViewManager.isShowing() + && mStatusBarKeyguardViewManager.isSecure(); + } + private void onLockedNotificationImportanceChange(OnDismissAction dismissAction) { mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); mActivityStarter.dismissKeyguardThenExecute(dismissAction, null, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java index 986a86d13958..cb6e30070fdd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java @@ -84,7 +84,7 @@ public class StatusBarWindowController implements Callback, Dumpable, Configurat } @VisibleForTesting - StatusBarWindowController(Context context, WindowManager windowManager, + public StatusBarWindowController(Context context, WindowManager windowManager, IActivityManager activityManager, DozeParameters dozeParameters) { mContext = context; mWindowManager = windowManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index 2a4336e809a0..4fa8321fa1e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -16,6 +16,8 @@ import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.InsetDrawable; import android.graphics.drawable.RippleDrawable; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.text.Layout; import android.text.TextPaint; import android.text.method.TransformationMethod; @@ -34,6 +36,7 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; +import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -61,6 +64,8 @@ public class SmartReplyView extends ViewGroup { private final SmartReplyConstants mConstants; private final KeyguardDismissUtil mKeyguardDismissUtil; + private final NotificationRemoteInputManager mRemoteInputManager; + private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); /** * The upper bound for the height of this view in pixels. Notifications are automatically @@ -109,6 +114,7 @@ public class SmartReplyView extends ViewGroup { super(context, attrs); mConstants = Dependency.get(SmartReplyConstants.class); mKeyguardDismissUtil = Dependency.get(KeyguardDismissUtil.class); + mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); mHeightUpperLimit = NotificationUtils.getFontScaledHeight(mContext, R.dimen.smart_reply_button_max_height); @@ -209,13 +215,15 @@ public class SmartReplyView extends ViewGroup { * notification are shown. */ public void addSmartActions(SmartActions smartActions, - SmartReplyController smartReplyController, NotificationData.Entry entry) { + SmartReplyController smartReplyController, NotificationData.Entry entry, + HeadsUpManager headsUpManager) { int numSmartActions = smartActions.actions.size(); for (int n = 0; n < numSmartActions; n++) { Notification.Action action = smartActions.actions.get(n); if (action.actionIntent != null) { Button actionButton = inflateActionButton( - getContext(), this, n, smartActions, smartReplyController, entry); + getContext(), this, n, smartActions, smartReplyController, entry, + headsUpManager); addView(actionButton); } } @@ -237,12 +245,22 @@ public class SmartReplyView extends ViewGroup { b.setText(choice); OnDismissAction action = () -> { + // TODO(b/111437455): Also for EDIT_CHOICES_BEFORE_SENDING_AUTO, depending on flags. + if (smartReplies.remoteInput.getEditChoicesBeforeSending() + == RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED) { + entry.remoteInputText = choice; + mRemoteInputManager.activateRemoteInput(b, + new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput, + smartReplies.pendingIntent); + return false; + } + smartReplyController.smartReplySent( entry, replyIndex, b.getText(), smartReplies.fromAssistant); Bundle results = new Bundle(); results.putString(smartReplies.remoteInput.getResultKey(), choice.toString()); Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - RemoteInput.addResultsToIntent(new RemoteInput[]{smartReplies.remoteInput}, intent, + RemoteInput.addResultsToIntent(new RemoteInput[] { smartReplies.remoteInput }, intent, results); RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE); entry.setHasSentReply(); @@ -274,7 +292,7 @@ public class SmartReplyView extends ViewGroup { @VisibleForTesting Button inflateActionButton(Context context, ViewGroup root, int actionIndex, SmartActions smartActions, SmartReplyController smartReplyController, - NotificationData.Entry entry) { + NotificationData.Entry entry, HeadsUpManager headsUpManager) { Notification.Action action = smartActions.actions.get(actionIndex); Button button = (Button) LayoutInflater.from(context).inflate( R.layout.smart_action_button, root, false); @@ -290,8 +308,12 @@ public class SmartReplyView extends ViewGroup { button.setOnClickListener(view -> getActivityStarter().startPendingIntentDismissingKeyguard( action.actionIntent, - () -> smartReplyController.smartActionClicked( - entry, actionIndex, action, smartActions.fromAssistant))); + () -> { + smartReplyController.smartActionClicked( + entry, actionIndex, action, smartActions.fromAssistant); + postOnUiThread(() -> + headsUpManager.removeNotification(entry.key, true)); + })); // TODO(b/119010281): handle accessibility @@ -301,6 +323,10 @@ public class SmartReplyView extends ViewGroup { return button; } + private void postOnUiThread(Runnable runnable) { + mMainThreadHandler.post(runnable); + } + @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(mContext, attrs); diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java index e811270a4450..f792d7d11e15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java @@ -18,6 +18,7 @@ import android.content.Context; import android.testing.LeakCheck; import android.testing.TestableContext; import android.util.ArrayMap; +import android.view.Display; public class SysuiTestableContext extends TestableContext implements SysUiServiceProvider { @@ -47,4 +48,15 @@ public class SysuiTestableContext extends TestableContext implements SysUiServic if (mComponents == null) mComponents = new ArrayMap<>(); mComponents.put(interfaceType, component); } + + @Override + public Context createDisplayContext(Display display) { + if (display == null) { + throw new IllegalArgumentException("display must not be null"); + } + + SysuiTestableContext context = + new SysuiTestableContext(getBaseContext().createDisplayContext(display)); + return context; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java new file mode 100644 index 000000000000..16ef63f1848a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2018 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.bubbles; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.app.IActivityManager; +import android.content.Context; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.NotificationTestHelper; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.StatusBarWindowController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class BubbleControllerTest extends SysuiTestCase { + + @Mock + private WindowManager mWindowManager; + @Mock + private IActivityManager mActivityManager; + @Mock + private DozeParameters mDozeParameters; + @Mock + private FrameLayout mStatusBarView; + + private TestableBubbleController mBubbleController; + private StatusBarWindowController mStatusBarWindowController; + + private NotificationTestHelper mNotificationTestHelper; + private ExpandableNotificationRow mRow; + private ExpandableNotificationRow mRow2; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + // Bubbles get added to status bar window view + mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager, + mActivityManager, mDozeParameters); + mStatusBarWindowController.add(mStatusBarView, 120 /* height */); + + // Need notifications for bubbles + mNotificationTestHelper = new NotificationTestHelper(mContext); + mRow = mNotificationTestHelper.createBubble(); + mRow2 = mNotificationTestHelper.createBubble(); + + mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController); + } + + @Test + public void testIsBubble() { + assertTrue(mRow.getEntry().isBubble()); + } + + @Test + public void testAddBubble() { + mBubbleController.addBubble(mRow.getEntry()); + assertTrue(mBubbleController.hasBubbles()); + } + + @Test + public void testHasBubbles() { + assertFalse(mBubbleController.hasBubbles()); + mBubbleController.addBubble(mRow.getEntry()); + assertTrue(mBubbleController.hasBubbles()); + } + + @Test + public void testRemoveBubble() { + mBubbleController.addBubble(mRow.getEntry()); + assertTrue(mBubbleController.hasBubbles()); + + mBubbleController.removeBubble(mRow.getEntry().key); + assertFalse(mStatusBarWindowController.getBubblesShowing()); + } + + @Test + public void testDismissStack() { + mBubbleController.addBubble(mRow.getEntry()); + mBubbleController.addBubble(mRow2.getEntry()); + assertTrue(mBubbleController.hasBubbles()); + + mBubbleController.dismissStack(); + assertFalse(mStatusBarWindowController.getBubblesShowing()); + } + + @Test + public void testIsStackExpanded() { + assertFalse(mBubbleController.isStackExpanded()); + mBubbleController.addBubble(mRow.getEntry()); + + BubbleStackView stackView = mBubbleController.getStackView(); + stackView.animateExpansion(true /* expanded */); + assertTrue(mBubbleController.isStackExpanded()); + + stackView.animateExpansion(false /* expanded */); + assertFalse(mBubbleController.isStackExpanded()); + } + + @Test + public void testCollapseStack() { + mBubbleController.addBubble(mRow.getEntry()); + mBubbleController.addBubble(mRow2.getEntry()); + + BubbleStackView stackView = mBubbleController.getStackView(); + stackView.animateExpansion(true /* expanded */); + assertTrue(mBubbleController.isStackExpanded()); + + mBubbleController.collapseStack(); + assertFalse(mBubbleController.isStackExpanded()); + } + + static class TestableBubbleController extends BubbleController { + + TestableBubbleController(Context context, + StatusBarWindowController statusBarWindowController) { + super(context); + mStatusBarWindowController = statusBarWindowController; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index 676463407f3f..e5464e0343f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -19,8 +19,10 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_BAR_STATE; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -37,7 +39,6 @@ import static java.lang.Thread.sleep; import android.content.Intent; import android.metrics.LogMaker; import android.support.test.filters.SmallTest; -import android.support.test.InstrumentationRegistry; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -48,12 +49,17 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.StatusBarStateController; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; +import org.mockito.Captor; +import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -65,13 +71,20 @@ public class QSTileImplTest extends SysuiTestCase { private TileImpl mTile; private QSTileHost mHost; private MetricsLogger mMetricsLogger; + private StatusBarStateController mStatusBarStateController; + + @Captor + private ArgumentCaptor<LogMaker> mLogCaptor; @Before public void setup() throws Exception { + MockitoAnnotations.initMocks(this); String spec = "spec"; mTestableLooper = TestableLooper.get(this); mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class); + mStatusBarStateController = + mDependency.injectMockDependency(StatusBarStateController.class); mHost = mock(QSTileHost.class); when(mHost.indexOf(spec)).thenReturn(POSITION); when(mHost.getContext()).thenReturn(mContext.getBaseContext()); @@ -88,19 +101,57 @@ public class QSTileImplTest extends SysuiTestCase { } @Test + public void testClick_Metrics_Status_Bar_Status() { + when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); + mTile.click(); + verify(mMetricsLogger).write(mLogCaptor.capture()); + assertEquals(StatusBarState.SHADE, mLogCaptor.getValue() + .getTaggedData(FIELD_STATUS_BAR_STATE)); + } + + @Test public void testSecondaryClick_Metrics() { mTile.secondaryClick(); verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK))); } @Test + public void testSecondaryClick_Metrics_Status_Bar_Status() { + when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); + mTile.secondaryClick(); + verify(mMetricsLogger).write(mLogCaptor.capture()); + assertEquals(StatusBarState.KEYGUARD, mLogCaptor.getValue() + .getTaggedData(FIELD_STATUS_BAR_STATE)); + } + + @Test public void testLongClick_Metrics() { mTile.longClick(); verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_LONG_PRESS))); } @Test - public void testPopulate() { + public void testLongClick_Metrics_Status_Bar_Status() { + when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); + mTile.click(); + verify(mMetricsLogger).write(mLogCaptor.capture()); + assertEquals(StatusBarState.SHADE_LOCKED, mLogCaptor.getValue() + .getTaggedData(FIELD_STATUS_BAR_STATE)); + } + + @Test + public void testPopulateWithLockedScreen() { + LogMaker maker = mock(LogMaker.class); + when(maker.setSubtype(anyInt())).thenReturn(maker); + when(maker.addTaggedData(anyInt(), any())).thenReturn(maker); + mTile.getState().value = true; + mTile.populate(maker); + verify(maker).addTaggedData(eq(FIELD_QS_VALUE), eq(1)); + verify(maker).addTaggedData(eq(FIELD_QS_POSITION), eq(POSITION)); + } + + @Test + public void testPopulateWithUnlockedScreen() { LogMaker maker = mock(LogMaker.class); when(maker.setSubtype(anyInt())).thenReturn(maker); when(maker.addTaggedData(anyInt(), any())).thenReturn(maker); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index 9bed59b16f6e..fb5e8753c935 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -33,9 +33,9 @@ import android.widget.RemoteViews; import com.android.systemui.R; import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import com.android.systemui.statusbar.notification.row.NotificationInflaterTest; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -86,7 +86,8 @@ public class NotificationTestHelper { * @throws Exception */ public ExpandableNotificationRow createRow(String pkg, int uid) throws Exception { - return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */); + return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */, + false /* isBubble */); } /** @@ -97,7 +98,8 @@ public class NotificationTestHelper { * @throws Exception */ public ExpandableNotificationRow createRow(Notification notification) throws Exception { - return generateRow(notification, PKG, UID, 0 /* extraInflationFlags */); + return generateRow(notification, PKG, UID, 0 /* extraInflationFlags */, + false /* isBubble */); } /** @@ -110,7 +112,8 @@ public class NotificationTestHelper { */ public ExpandableNotificationRow createRow(@InflationFlag int extraInflationFlags) throws Exception { - return generateRow(createNotification(), PKG, UID, extraInflationFlags); + return generateRow(createNotification(), PKG, UID, extraInflationFlags, + false /* isBubble */); } /** @@ -131,12 +134,20 @@ public class NotificationTestHelper { return createGroup(2); } + /** + * Retursn an {@link ExpandableNotificationRow} that should be a bubble. + */ + public ExpandableNotificationRow createBubble() throws Exception { + return createRow(PKG, UID, false /* isGroupSummary */, null /* groupKey */, + true /* isBubble */); + } + private ExpandableNotificationRow createGroupSummary(String groupkey) throws Exception { - return createRow(PKG, UID, true /* isGroupSummary */, groupkey); + return createRow(PKG, UID, true /* isGroupSummary */, groupkey, false); } private ExpandableNotificationRow createGroupChild(String groupkey) throws Exception { - return createRow(PKG, UID, false /* isGroupSummary */, groupkey); + return createRow(PKG, UID, false /* isGroupSummary */, groupkey, false); } /** @@ -146,6 +157,7 @@ public class NotificationTestHelper { * @param uid uid used for creating a {@link StatusBarNotification} * @param isGroupSummary whether the notification row is a group summary * @param groupKey the group key for the notification group used across notifications + * @param isBubble * @return a row with that's either a standalone notification or a group notification if the * groupKey is non-null * @throws Exception @@ -154,10 +166,10 @@ public class NotificationTestHelper { String pkg, int uid, boolean isGroupSummary, - @Nullable String groupKey) + @Nullable String groupKey, boolean isBubble) throws Exception { Notification notif = createNotification(isGroupSummary, groupKey); - return generateRow(notif, pkg, uid, 0 /* inflationFlags */); + return generateRow(notif, pkg, uid, 0 /* inflationFlags */, isBubble); } /** @@ -202,7 +214,7 @@ public class NotificationTestHelper { Notification notification, String pkg, int uid, - @InflationFlag int extraInflationFlags) + @InflationFlag int extraInflationFlags, boolean isBubble) throws Exception { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( mContext.LAYOUT_INFLATER_SERVICE); @@ -232,6 +244,7 @@ public class NotificationTestHelper { entry.channel = new NotificationChannel( notification.getChannelId(), notification.getChannelId(), IMPORTANCE_DEFAULT); entry.channel.setBlockableSystem(true); + entry.setIsBubble(isBubble); row.setEntry(entry); row.getNotificationInflater().addInflationFlags(extraInflationFlags); NotificationInflaterTest.runThenWaitForInflation( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java new file mode 100644 index 000000000000..73f3b43aad62 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import android.graphics.PixelFormat; +import android.hardware.display.DisplayManager; +import android.hardware.display.VirtualDisplay; +import android.media.ImageReader; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; +import android.view.Display; +import android.view.DisplayInfo; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestableContext; +import com.android.systemui.statusbar.CommandQueue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** atest NavigationBarButtonTest */ +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +@SmallTest +public class NavigationBarButtonTest extends SysuiTestCase { + + private ImageReader mReader; + private NavigationBarView mNavBar; + private VirtualDisplay mVirtualDisplay; + + @Before + public void setup() { + final Display display = createVirtualDisplay(); + final SysuiTestableContext context = + (SysuiTestableContext) mContext.createDisplayContext(display); + context.putComponent(CommandQueue.class, mock(CommandQueue.class)); + + mNavBar = new NavigationBarView(context, null); + } + + private Display createVirtualDisplay() { + final String displayName = "NavVirtualDisplay"; + final DisplayInfo displayInfo = new DisplayInfo(); + mContext.getDisplay().getDisplayInfo(displayInfo); + + final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + + mReader = ImageReader.newInstance(displayInfo.logicalWidth, + displayInfo.logicalHeight, PixelFormat.RGBA_8888, 2); + + assertNotNull("ImageReader must not be null", mReader); + + mVirtualDisplay = displayManager.createVirtualDisplay(displayName, displayInfo.logicalWidth, + displayInfo.logicalHeight, displayInfo.logicalDensityDpi, mReader.getSurface(), + 0 /*flags*/); + + assertNotNull("virtual display must not be null", mVirtualDisplay); + + return mVirtualDisplay.getDisplay(); + } + + @After + public void tearDown() { + releaseDisplay(); + } + + private void releaseDisplay() { + mVirtualDisplay.release(); + mReader.close(); + } + + @Test + public void testRecentsButtonDisabledOnSecondaryDisplay() { + assertTrue("The recents button must be disabled", + mNavBar.isRecentsButtonDisabled()); + } +} + + + diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java index c3bc5110febe..ee39e10b4165 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java @@ -32,14 +32,19 @@ import android.testing.TestableLooper; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.AmbientPulseManager; +import com.android.systemui.statusbar.notification.AlertTransferListener; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationData.Entry; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.policy.HeadsUpManager; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -49,13 +54,15 @@ import java.util.HashMap; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { - @Rule - public MockitoRule rule = MockitoJUnit.rule(); + @Rule public MockitoRule rule = MockitoJUnit.rule(); private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper; private NotificationGroupManager mGroupManager; private AmbientPulseManager mAmbientPulseManager; private HeadsUpManager mHeadsUpManager; + @Mock private NotificationEntryManager mNotificationEntryManager; + @Captor private ArgumentCaptor<AlertTransferListener> mListenerCaptor; + private AlertTransferListener mAlertTransferListener; private final HashMap<String, Entry> mPendingEntries = new HashMap<>(); private final NotificationGroupTestHelper mGroupTestHelper = new NotificationGroupTestHelper(mContext); @@ -67,15 +74,19 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mDependency.injectTestDependency(AmbientPulseManager.class, mAmbientPulseManager); mHeadsUpManager = new HeadsUpManager(mContext) {}; + when(mNotificationEntryManager.getPendingNotificationsIterator()) + .thenReturn(mPendingEntries.values()); + mGroupManager = new NotificationGroupManager(); mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager); mGroupManager.setHeadsUpManager(mHeadsUpManager); mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(); mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager); - mGroupAlertTransferHelper.setPendingEntries(mPendingEntries); - mGroupManager.addOnGroupChangeListener(mGroupAlertTransferHelper); + mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager); + verify(mNotificationEntryManager).setAlertTransferListener(mListenerCaptor.capture()); + mAlertTransferListener = mListenerCaptor.getValue(); mHeadsUpManager.addListener(mGroupAlertTransferHelper); mAmbientPulseManager.addListener(mGroupAlertTransferHelper); } @@ -110,7 +121,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { // Add second child notification so that summary is no longer suppressed. mPendingEntries.put(childEntry2.key, childEntry2); - mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2); + mAlertTransferListener.onPendingEntryAdded(childEntry2); mGroupManager.onEntryAdded(childEntry2); // The alert state should transfer back to the summary as there is now more than one @@ -137,7 +148,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { // Add second child notification so that summary is no longer suppressed. mPendingEntries.put(childEntry2.key, childEntry2); - mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2); + mAlertTransferListener.onPendingEntryAdded(childEntry2); mGroupManager.onEntryAdded(childEntry2); // Dozing changed so no reason to re-alert summary. @@ -175,7 +186,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) .thenReturn(true); - mGroupAlertTransferHelper.onInflationFinished(childEntry); + mAlertTransferListener.onEntryReinflated(childEntry); // Alert is immediately removed from summary, and we show child as its content is inflated. assertFalse(mHeadsUpManager.isAlerting(summaryEntry.key)); @@ -199,13 +210,13 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { // Add second child notification so that summary is no longer suppressed. mPendingEntries.put(childEntry2.key, childEntry2); - mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2); + mAlertTransferListener.onPendingEntryAdded(childEntry2); mGroupManager.onEntryAdded(childEntry2); // Child entry finishes its inflation. when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag())) .thenReturn(true); - mGroupAlertTransferHelper.onInflationFinished(childEntry); + mAlertTransferListener.onEntryReinflated(childEntry); verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager .getContentFlag()); @@ -225,7 +236,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mGroupManager.onEntryAdded(summaryEntry); mGroupManager.onEntryAdded(childEntry); - mGroupAlertTransferHelper.cleanUpPendingAlertInfo(childEntry.key); + mAlertTransferListener.onEntryRemoved(childEntry); assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java index cdaa2420186f..2805908d114a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java @@ -61,6 +61,10 @@ import org.mockito.MockitoAnnotations; @RunWithLooper @SmallTest public class QuickStepControllerTest extends SysuiTestCase { + private static final int NAVBAR_WIDTH = 1000; + private static final int NAVBAR_HEIGHT = 300; + private static final int EDGE_THRESHOLD = 100; + private QuickStepController mController; private NavigationBarView mNavigationBarView; private StatusBar mStatusBar; @@ -73,6 +77,8 @@ public class QuickStepControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); final ButtonDispatcher backButton = mock(ButtonDispatcher.class); mResources = mock(Resources.class); + doReturn(EDGE_THRESHOLD).when(mResources) + .getDimensionPixelSize(R.dimen.navigation_bar_edge_swipe_threshold); mProxyService = mock(OverviewProxyService.class); mProxy = mock(IOverviewProxy.Stub.class); @@ -109,7 +115,8 @@ public class QuickStepControllerTest extends SysuiTestCase { public void testNoGesturesWhenSwipeUpDisabled() throws Exception { doReturn(false).when(mProxyService).shouldShowSwipeUpUI(); mController.setGestureActions(mockAction(true), null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */); + null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, + null /* rightEdgeSwipe */); MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1); assertFalse(mController.onInterceptTouchEvent(ev)); @@ -124,7 +131,8 @@ public class QuickStepControllerTest extends SysuiTestCase { // Add enabled gesture action NavigationGestureAction action = mockAction(true); mController.setGestureActions(action, null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */); + null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, + null /* rightEdgeSwipe */); assertFalse(mController.onInterceptTouchEvent(ev)); verify(mNavigationBarView, times(1)).requestUnbufferedDispatch(ev); @@ -140,7 +148,8 @@ public class QuickStepControllerTest extends SysuiTestCase { // Add enabled gesture action mController.setGestureActions(mockAction(true), null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */); + null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, + null /* rightEdgeSwipe */); // Set the gesture on deadzone doReturn(null).when(mProxyService).getProxy(); @@ -165,7 +174,8 @@ public class QuickStepControllerTest extends SysuiTestCase { @Test public void testOnTouchIgnoredDownEventAfterOnIntercept() { mController.setGestureActions(mockAction(true), null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */); + null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, + null /* rightEdgeSwipe */); MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1); assertFalse(touch(ev)); @@ -178,29 +188,45 @@ public class QuickStepControllerTest extends SysuiTestCase { @Test public void testGesturesCallCorrectAction() throws Exception { + doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth(); + doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight(); + NavigationGestureAction swipeUp = mockAction(true); NavigationGestureAction swipeDown = mockAction(true); NavigationGestureAction swipeLeft = mockAction(true); NavigationGestureAction swipeRight = mockAction(true); - mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight); + NavigationGestureAction swipeLeftFromEdge = mockAction(true); + NavigationGestureAction swipeRightFromEdge = mockAction(true); + mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, swipeLeftFromEdge, + swipeRightFromEdge); // Swipe Up assertGestureTriggersAction(swipeUp, 1, 100, 5, 1); // Swipe Down assertGestureTriggersAction(swipeDown, 1, 1, 5, 100); // Swipe Left - assertGestureTriggersAction(swipeLeft, 100, 1, 5, 1); + assertGestureTriggersAction(swipeLeft, NAVBAR_WIDTH / 2, 1, 5, 1); // Swipe Right - assertGestureTriggersAction(swipeRight, 1, 1, 100, 5); + assertGestureTriggersAction(swipeRight, NAVBAR_WIDTH / 2, 1, NAVBAR_WIDTH, 5); + // Swipe Left from Edge + assertGestureTriggersAction(swipeLeftFromEdge, NAVBAR_WIDTH, 1, 5, 1); + // Swipe Right from Edge + assertGestureTriggersAction(swipeRightFromEdge, 0, 1, NAVBAR_WIDTH, 5); } @Test public void testGesturesCallCorrectActionLandscape() throws Exception { + doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getWidth(); + doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getHeight(); + NavigationGestureAction swipeUp = mockAction(true); NavigationGestureAction swipeDown = mockAction(true); NavigationGestureAction swipeLeft = mockAction(true); NavigationGestureAction swipeRight = mockAction(true); - mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight); + NavigationGestureAction swipeLeftFromEdge = mockAction(true); + NavigationGestureAction swipeRightFromEdge = mockAction(true); + mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, swipeLeftFromEdge, + swipeRightFromEdge); // In landscape mController.setBarState(false /* isRTL */, NAV_BAR_RIGHT); @@ -208,34 +234,50 @@ public class QuickStepControllerTest extends SysuiTestCase { // Swipe Up assertGestureTriggersAction(swipeRight, 1, 100, 5, 1); // Swipe Down - assertGestureTriggersAction(swipeLeft, 1, 1, 5, 100); + assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, NAVBAR_WIDTH); // Swipe Left assertGestureTriggersAction(swipeUp, 100, 1, 5, 1); // Swipe Right assertGestureTriggersAction(swipeDown, 1, 1, 100, 5); + // Swipe Up from Edge + assertGestureTriggersAction(swipeRightFromEdge, 1, NAVBAR_WIDTH, 5, 0); + // Swipe Down from Edge + assertGestureTriggersAction(swipeLeftFromEdge, 0, 1, 0, NAVBAR_WIDTH); } @Test public void testGesturesCallCorrectActionSeascape() throws Exception { + doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getWidth(); + doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getHeight(); + mController.setBarState(false /* isRTL */, NAV_BAR_LEFT); NavigationGestureAction swipeUp = mockAction(true); NavigationGestureAction swipeDown = mockAction(true); NavigationGestureAction swipeLeft = mockAction(true); NavigationGestureAction swipeRight = mockAction(true); - mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight); + NavigationGestureAction swipeLeftFromEdge = mockAction(true); + NavigationGestureAction swipeRightFromEdge = mockAction(true); + mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, swipeLeftFromEdge, + swipeRightFromEdge); // Swipe Up - assertGestureTriggersAction(swipeLeft, 1, 100, 5, 1); + assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, 1); // Swipe Down - assertGestureTriggersAction(swipeRight, 1, 1, 5, 100); + assertGestureTriggersAction(swipeRight, 1, NAVBAR_WIDTH / 2, 5, NAVBAR_WIDTH); // Swipe Left assertGestureTriggersAction(swipeDown, 100, 1, 5, 1); // Swipe Right assertGestureTriggersAction(swipeUp, 1, 1, 100, 5); + // Swipe Up from Edge + assertGestureTriggersAction(swipeLeftFromEdge, 1, NAVBAR_WIDTH, 5, 0); + // Swipe Down from Edge + assertGestureTriggersAction(swipeRightFromEdge, 0, 1, 0, NAVBAR_WIDTH); } @Test public void testGesturesCallCorrectActionRTL() throws Exception { + doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth(); + doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight(); mController.setBarState(true /* isRTL */, NAV_BAR_BOTTOM); // The swipe gestures below are for LTR, so RTL in portrait will be swapped @@ -243,20 +285,29 @@ public class QuickStepControllerTest extends SysuiTestCase { NavigationGestureAction swipeDown = mockAction(true); NavigationGestureAction swipeLeft = mockAction(true); NavigationGestureAction swipeRight = mockAction(true); - mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight); + NavigationGestureAction swipeLeftFromEdge = mockAction(true); + NavigationGestureAction swipeRightFromEdge = mockAction(true); + mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, swipeLeftFromEdge, + swipeRightFromEdge); // Swipe Up in RTL assertGestureTriggersAction(swipeUp, 1, 100, 5, 1); // Swipe Down in RTL assertGestureTriggersAction(swipeDown, 1, 1, 5, 100); // Swipe Left in RTL - assertGestureTriggersAction(swipeRight, 100, 1, 5, 1); + assertGestureTriggersAction(swipeRight, NAVBAR_WIDTH / 2, 1, 5, 1); // Swipe Right in RTL - assertGestureTriggersAction(swipeLeft, 1, 1, 100, 5); + assertGestureTriggersAction(swipeLeft, NAVBAR_WIDTH / 2, 1, NAVBAR_WIDTH, 0); + // Swipe Left from Edge + assertGestureTriggersAction(swipeRightFromEdge, NAVBAR_WIDTH, 1, 5, 1); + // Swipe Right from Edge + assertGestureTriggersAction(swipeLeftFromEdge, 0, 1, NAVBAR_WIDTH, 5); } @Test public void testGesturesCallCorrectActionLandscapeRTL() throws Exception { + doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getWidth(); + doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getHeight(); mController.setBarState(true /* isRTL */, NAV_BAR_RIGHT); // The swipe gestures below are for LTR, so RTL in landscape will be swapped @@ -264,20 +315,29 @@ public class QuickStepControllerTest extends SysuiTestCase { NavigationGestureAction swipeDown = mockAction(true); NavigationGestureAction swipeLeft = mockAction(true); NavigationGestureAction swipeRight = mockAction(true); - mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight); + NavigationGestureAction swipeLeftFromEdge = mockAction(true); + NavigationGestureAction swipeRightFromEdge = mockAction(true); + mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, swipeLeftFromEdge, + swipeRightFromEdge); // Swipe Up - assertGestureTriggersAction(swipeLeft, 1, 100, 5, 1); + assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, 1); // Swipe Down - assertGestureTriggersAction(swipeRight, 1, 1, 5, 100); + assertGestureTriggersAction(swipeRight, 1, NAVBAR_WIDTH / 2, 5, NAVBAR_WIDTH); // Swipe Left assertGestureTriggersAction(swipeUp, 100, 1, 5, 1); // Swipe Right assertGestureTriggersAction(swipeDown, 1, 1, 100, 5); + // Swipe Up from Edge + assertGestureTriggersAction(swipeLeftFromEdge, 1, NAVBAR_WIDTH, 5, 0); + // Swipe Down from Edge + assertGestureTriggersAction(swipeRightFromEdge, 0, 1, 0, NAVBAR_WIDTH); } @Test public void testGesturesCallCorrectActionSeascapeRTL() throws Exception { + doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getWidth(); + doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getHeight(); mController.setBarState(true /* isRTL */, NAV_BAR_LEFT); // The swipe gestures below are for LTR, so RTL in seascape will be swapped @@ -285,16 +345,23 @@ public class QuickStepControllerTest extends SysuiTestCase { NavigationGestureAction swipeDown = mockAction(true); NavigationGestureAction swipeLeft = mockAction(true); NavigationGestureAction swipeRight = mockAction(true); - mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight); + NavigationGestureAction swipeLeftFromEdge = mockAction(true); + NavigationGestureAction swipeRightFromEdge = mockAction(true); + mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, swipeLeftFromEdge, + swipeRightFromEdge); // Swipe Up - assertGestureTriggersAction(swipeRight, 1, 100, 5, 1); + assertGestureTriggersAction(swipeRight, 1, NAVBAR_WIDTH / 2, 5, 1); // Swipe Down - assertGestureTriggersAction(swipeLeft, 1, 1, 5, 100); + assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, NAVBAR_WIDTH); // Swipe Left assertGestureTriggersAction(swipeDown, 100, 1, 5, 1); // Swipe Right assertGestureTriggersAction(swipeUp, 1, 1, 100, 5); + // Swipe Up from Edge + assertGestureTriggersAction(swipeRightFromEdge, 1, NAVBAR_WIDTH, 5, 0); + // Swipe Down from Edge + assertGestureTriggersAction(swipeLeftFromEdge, 0, 1, 0, NAVBAR_WIDTH); } @Test @@ -305,7 +372,8 @@ public class QuickStepControllerTest extends SysuiTestCase { // Add enabled gesture action NavigationGestureAction action = mockAction(true); mController.setGestureActions(action, null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */); + null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, + null /* rightEdgeSwipe */); // Touch down to begin swipe MotionEvent downEvent = event(MotionEvent.ACTION_DOWN, 1, 100); @@ -326,7 +394,8 @@ public class QuickStepControllerTest extends SysuiTestCase { NavigationGestureAction action = mockAction(true); doReturn(false).when(action).canRunWhenNotificationsShowing(); mController.setGestureActions(action, null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */); + null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, + null /* rightEdgeSwipe */); // Show the notifications doReturn(false).when(mNavigationBarView).isNotificationsFullyCollapsed(); @@ -351,7 +420,8 @@ public class QuickStepControllerTest extends SysuiTestCase { public void testActionCannotPerform() throws Exception { NavigationGestureAction action = mockAction(true); mController.setGestureActions(action, null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */); + null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, + null /* rightEdgeSwipe */); // Cannot perform action doReturn(false).when(action).canPerformAction(); @@ -374,13 +444,17 @@ public class QuickStepControllerTest extends SysuiTestCase { @Test public void testQuickScrub() throws Exception { + doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth(); + doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight(); QuickScrubAction action = spy(new QuickScrubAction(mNavigationBarView, mProxyService)); mController.setGestureActions(null /* swipeUpAction */, null /* swipeDownAction */, - null /* swipeLeftAction */, action); + null /* swipeLeftAction */, action, null /* leftEdgeSwipe */, + null /* rightEdgeSwipe */); + int x = NAVBAR_WIDTH / 2; int y = 20; // Set the layout and other padding to make sure the scrub fraction is calculated correctly - action.onLayout(true, 0, 0, 400, 100); + action.onLayout(true, 0, 0, NAVBAR_WIDTH, NAVBAR_HEIGHT); doReturn(0).when(mNavigationBarView).getPaddingLeft(); doReturn(0).when(mNavigationBarView).getPaddingRight(); doReturn(0).when(mNavigationBarView).getPaddingStart(); @@ -393,14 +467,14 @@ public class QuickStepControllerTest extends SysuiTestCase { doReturn(true).when(mNavigationBarView).isQuickScrubEnabled(); // Touch down - MotionEvent downEvent = event(MotionEvent.ACTION_DOWN, 0, y); + MotionEvent downEvent = event(MotionEvent.ACTION_DOWN, x, y); assertFalse(touch(downEvent)); assertNull(mController.getCurrentAction()); verify(mProxy, times(1)).onPreMotionEvent(mNavigationBarView.getDownHitTarget()); verify(mProxy, times(1)).onMotionEvent(downEvent); // Move to start trigger action from gesture - MotionEvent moveEvent1 = event(MotionEvent.ACTION_MOVE, 100, y); + MotionEvent moveEvent1 = event(MotionEvent.ACTION_MOVE, x + 100, y); assertTrue(touch(moveEvent1)); assertEquals(action, mController.getCurrentAction()); verify(action, times(1)).onGestureStart(moveEvent1); @@ -410,11 +484,13 @@ public class QuickStepControllerTest extends SysuiTestCase { verify(mProxy, never()).onMotionEvent(moveEvent1); // Move again for scrub - MotionEvent moveEvent2 = event(MotionEvent.ACTION_MOVE, 200, y); + float fraction = 3f / 4; + x = (int) (NAVBAR_WIDTH * fraction); + MotionEvent moveEvent2 = event(MotionEvent.ACTION_MOVE, x, y); assertTrue(touch(moveEvent2)); assertEquals(action, mController.getCurrentAction()); - verify(action, times(1)).onGestureMove(200, y); - verify(mProxy, times(1)).onQuickScrubProgress(1f / 2); + verify(action, times(1)).onGestureMove(x, y); + verify(mProxy, times(1)).onQuickScrubProgress(fraction); verify(mProxy, never()).onMotionEvent(moveEvent2); // Action up @@ -430,7 +506,8 @@ public class QuickStepControllerTest extends SysuiTestCase { public void testQuickStep() throws Exception { QuickStepAction action = new QuickStepAction(mNavigationBarView, mProxyService); mController.setGestureActions(action, null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */); + null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, + null /* rightEdgeSwipe */); // Notifications are up, should prevent quickstep doReturn(false).when(mNavigationBarView).isNotificationsFullyCollapsed(); @@ -466,7 +543,8 @@ public class QuickStepControllerTest extends SysuiTestCase { public void testLongPressPreventDetection() throws Exception { NavigationGestureAction action = mockAction(true); mController.setGestureActions(action, null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */); + null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, + null /* rightEdgeSwipe */); // Start the drag up assertFalse(touch(MotionEvent.ACTION_DOWN, 100, 1)); @@ -488,23 +566,21 @@ public class QuickStepControllerTest extends SysuiTestCase { @Test public void testHitTargetDragged() throws Exception { - final int navbarWidth = 1000; - final int navbarHeight = 1000; ButtonDispatcher button = mock(ButtonDispatcher.class); - FakeLocationView buttonView = spy(new FakeLocationView(mContext, navbarWidth / 2, - navbarHeight / 2)); + FakeLocationView buttonView = spy(new FakeLocationView(mContext, NAVBAR_WIDTH / 2, + NAVBAR_HEIGHT / 2)); doReturn(buttonView).when(button).getCurrentView(); NavigationGestureAction action = mockAction(true); - mController.setGestureActions(action, action, action, action); + mController.setGestureActions(action, action, action, action, action, action); // Setup getting the hit target doReturn(HIT_TARGET_HOME).when(action).requiresTouchDownHitTarget(); - doReturn(true).when(action).requiresDragWithHitTarget(); + doReturn(true).when(action).allowHitTargetToMoveOverDrag(); doReturn(HIT_TARGET_HOME).when(mNavigationBarView).getDownHitTarget(); doReturn(button).when(mNavigationBarView).getHomeButton(); - doReturn(navbarWidth).when(mNavigationBarView).getWidth(); - doReturn(navbarHeight).when(mNavigationBarView).getHeight(); + doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth(); + doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight(); // Portrait assertGestureDragsHitTargetAllDirections(buttonView, false /* isRTL */, NAV_BAR_BOTTOM); @@ -525,6 +601,25 @@ public class QuickStepControllerTest extends SysuiTestCase { assertGestureDragsHitTargetAllDirections(buttonView, true /* isRTL */, NAV_BAR_LEFT); } + @Test + public void testNoEdgeActionsTriggerNormalActions() { + doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth(); + doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight(); + + NavigationGestureAction swipeUp = mockAction(true); + NavigationGestureAction swipeDown = mockAction(true); + NavigationGestureAction swipeLeft = mockAction(true); + NavigationGestureAction swipeRight = mockAction(true); + mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, + null /* swipeLeftFromEdgeAction */, + null /* swipeLeftFromEdgeAction */); + + // Swipe Left from Edge + assertGestureTriggersAction(swipeLeft, NAVBAR_WIDTH, 1, 5, 1); + // Swipe Right from Edge + assertGestureTriggersAction(swipeRight, 0, 1, NAVBAR_WIDTH, 5); + } + private void assertGestureDragsHitTargetAllDirections(View buttonView, boolean isRTL, int navPos) { mController.setBarState(isRTL, navPos); @@ -589,7 +684,6 @@ public class QuickStepControllerTest extends SysuiTestCase { reset(buttonView); } - private MotionEvent event(int action, float x, float y) { final MotionEvent event = mock(MotionEvent.class); doReturn(x).when(event).getX(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 27123e435525..02f894922959 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -70,7 +70,7 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mock(NotificationPanelView.class), mock(HeadsUpManagerPhone.class), statusBarWindowView, mock(NotificationListContainerViewGroup.class), mock(DozeScrimController.class), mock(ScrimController.class), - mock(ActivityLaunchAnimator.class)); + mock(ActivityLaunchAnimator.class), mock(StatusBarKeyguardViewManager.class)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java index 506fa974944a..c5bac9242b8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java @@ -98,6 +98,7 @@ public class SmartReplyViewTest extends SysuiTestCase { private Notification mNotification; @Mock ActivityStarter mActivityStarter; + @Mock HeadsUpManager mHeadsUpManager; @Before public void setUp() { @@ -434,7 +435,8 @@ public class SmartReplyViewTest extends SysuiTestCase { mView.addSmartActions( new SmartReplyView.SmartActions(createActions(actionTitles), false), mLogger, - mEntry); + mEntry, + mHeadsUpManager); } private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) { @@ -442,7 +444,8 @@ public class SmartReplyViewTest extends SysuiTestCase { mView.addSmartActions( new SmartReplyView.SmartActions(createActions(actionTitles), false), mLogger, - mEntry); + mEntry, + mHeadsUpManager); } private ViewGroup buildExpectedView(CharSequence[] choices, int lineCount) { @@ -747,7 +750,7 @@ public class SmartReplyViewTest extends SysuiTestCase { private Button inflateActionButton(Notification.Action action) { return mView.inflateActionButton(getContext(), mView, 0, new SmartReplyView.SmartActions(Collections.singletonList(action), false), - mLogger, mEntry); + mLogger, mEntry, mHeadsUpManager); } @Test diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ar/strings.xml b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rXC/strings.xml index 2cebf40c0cba..c5ed4158b3ef 100644 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ar/strings.xml +++ b/packages/overlays/ExperimentNavigationBarDefaultOverlay/res/values-en-rXC/strings.xml @@ -19,5 +19,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"تجربة شريط التنقُّل النحيف"</string> + <string name="experiment_navigationbar_overlay" msgid="3475628315769912732">"Default Navigation Bar Experiment (48dp)"</string> </resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-az/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-az/strings.xml deleted file mode 100644 index 266c051abc7d..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-az/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Sabit Naviqasiya Paneli Təcrübəsi"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-b+sr+Latn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-b+sr+Latn/strings.xml deleted file mode 100644 index dc57a3f6a076..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-b+sr+Latn/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperiment sa tankom trakom za navigaciju"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-be/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-be/strings.xml deleted file mode 100644 index 8b53bcdfe9d7..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-be/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Эксперымент з тонкай панэллю навігацыі"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bg/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bg/strings.xml deleted file mode 100644 index 4bf000e193c0..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bg/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Експеримент с тънка лента за навигация"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bs/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bs/strings.xml deleted file mode 100644 index 981420945aea..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-bs/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperiment s tankom trakom za navigaciju"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ca/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ca/strings.xml deleted file mode 100644 index 4a9b598158e6..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ca/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experiment amb barra de navegació fina"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-da/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-da/strings.xml deleted file mode 100644 index 12bb2f23fe1e..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-da/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Test med smal navigationslinje"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-de/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-de/strings.xml deleted file mode 100644 index 960a7d94e7ae..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-de/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experiment mit schmaler Navigationsleiste"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-el/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-el/strings.xml deleted file mode 100644 index 119b7e611cf9..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-el/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Πείραμα λεπτής γραμμής πλοήγησης"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rAU/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rAU/strings.xml deleted file mode 100644 index 5ebf40348199..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rAU/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Slim Navigation Bar Experiment"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rCA/strings.xml deleted file mode 100644 index 5ebf40348199..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rCA/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Slim Navigation Bar Experiment"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rGB/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rGB/strings.xml deleted file mode 100644 index 5ebf40348199..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rGB/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Slim Navigation Bar Experiment"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rIN/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rIN/strings.xml deleted file mode 100644 index 5ebf40348199..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rIN/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Slim Navigation Bar Experiment"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rXC/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rXC/strings.xml index 79884ce615cf..40d9fbca6ec9 100644 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rXC/strings.xml +++ b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-en-rXC/strings.xml @@ -19,5 +19,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Slim Navigation Bar Experiment"</string> + <string name="experiment_navigationbar_overlay" msgid="9207872199884142345">"Slim Navigation Bar Experiment (24dp)"</string> </resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-es-rUS/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-es-rUS/strings.xml deleted file mode 100644 index 00b044459777..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-es-rUS/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experimento de barra de navegación delgada"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-es/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-es/strings.xml deleted file mode 100644 index 58bdaa26115b..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-es/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experimento de barra navegación delgada"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-et/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-et/strings.xml deleted file mode 100644 index 4c5023cf1d14..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-et/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Kitsa navigeerimisriba katse"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-eu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-eu/strings.xml deleted file mode 100644 index fc16eeb8dc3e..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-eu/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Nabigazio-barra finaren esperimentua"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fa/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fa/strings.xml deleted file mode 100644 index e4246d79decb..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fa/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"آزمایش نوار پیمایش باریک"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fi/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fi/strings.xml deleted file mode 100644 index 9385a470368f..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fi/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Ohuen navigointipalkin kokeilu"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fr-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fr-rCA/strings.xml deleted file mode 100644 index d22848beda3d..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fr-rCA/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Expérience de barre de navigation mince"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fr/strings.xml deleted file mode 100644 index cfa1db2a06d3..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-fr/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Test relatif à la barre de navigation fine"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-gl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-gl/strings.xml deleted file mode 100644 index d2194ce1a820..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-gl/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experimento de barra de navegación estreita"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hi/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hi/strings.xml deleted file mode 100644 index d51cee34f895..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hi/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"स्लिम नेविगेशन बार प्रयोग"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hr/strings.xml deleted file mode 100644 index 410c58ef2cbc..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hr/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperiment s tankom navigacijskom trakom"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hu/strings.xml deleted file mode 100644 index d7eafed699ab..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hu/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Kísérleti keskeny navigációs sáv"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hy/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hy/strings.xml deleted file mode 100644 index b0200cab636b..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-hy/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Նավարկման նեղ գոտու փորձարկում"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-in/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-in/strings.xml deleted file mode 100644 index 3e0bded06e68..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-in/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperimen Menu Navigasi Ramping"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-is/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-is/strings.xml deleted file mode 100644 index 03ccaf3de068..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-is/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Tilraun með þunna yfirlitsstiku"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-it/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-it/strings.xml deleted file mode 100644 index f7c5d253e5ab..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-it/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Esperimento Barra di navigazione sottile"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-iw/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-iw/strings.xml deleted file mode 100644 index 0d0ec2c7c2a5..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-iw/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"ניסוי של סרגל ניווט דק"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ja/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ja/strings.xml deleted file mode 100644 index a3d6874e84f6..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ja/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"スリム ナビゲーション バー テスト"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ka/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ka/strings.xml deleted file mode 100644 index ffddf3bee802..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ka/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"ნავიგაციის მჭიდრო ზოლის ექსპერიმენტი"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-kk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-kk/strings.xml deleted file mode 100644 index f34ac0810127..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-kk/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Жіңішке навигация жолағы (эксперимент)"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-km/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-km/strings.xml deleted file mode 100644 index 114a78270d79..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-km/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"ការពិសោធនៃរបាររុករកស្ដើង"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ko/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ko/strings.xml deleted file mode 100644 index fca02c3e49bd..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ko/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"슬림한 탐색 메뉴 실험"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ky/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ky/strings.xml deleted file mode 100644 index 449de4fadfbc..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ky/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Чакан чабыттоо тилкесин сыноо"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lo/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lo/strings.xml deleted file mode 100644 index 6ec48ca13d19..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lo/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"ການທົດລອງແຖບການນຳທາງແບບບາງ"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lt/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lt/strings.xml deleted file mode 100644 index 1df54aa3fc02..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lt/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Plonos naršymo juostos eksperimentas"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lv/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lv/strings.xml deleted file mode 100644 index 5c6c5658406b..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-lv/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Plānas navigācijas joslas eksperiments"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mk/strings.xml deleted file mode 100644 index 3517813f74ad..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mk/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Експеримент со тенка лента за навигација"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mn/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mn/strings.xml deleted file mode 100644 index a2282c4c71c9..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mn/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Нимгэн навигацийн самбарын туршилт"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mr/strings.xml deleted file mode 100644 index c714370cdc7e..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-mr/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"स्लिम नॅव्हिगेशन बार प्रयोग"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ms/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ms/strings.xml deleted file mode 100644 index 68f831e39b46..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ms/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Percubaan Bar Navigasi Langsing"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-my/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-my/strings.xml deleted file mode 100644 index 84db27962308..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-my/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"ပါးလွှာသော လမ်းညွှန်ဘား စမ်းသပ်မှု"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-nb/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-nb/strings.xml deleted file mode 100644 index e1ff863ac5c8..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-nb/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperiment med tynn navigasjonsrad"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-nl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-nl/strings.xml deleted file mode 100644 index 01190bc624c8..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-nl/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experiment voor smalle navigatiebalk"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pl/strings.xml deleted file mode 100644 index 1742aadbcdd3..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pl/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperyment z wąskim paskiem nawigacyjnym"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt-rBR/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt-rBR/strings.xml deleted file mode 100644 index 22194b7e1ca5..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt-rBR/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experimento de barra de navegação fina"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt-rPT/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt-rPT/strings.xml deleted file mode 100644 index f6c030983578..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt-rPT/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experiência de barra de navegação fina"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt/strings.xml deleted file mode 100644 index 22194b7e1ca5..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-pt/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experimento de barra de navegação fina"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ro/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ro/strings.xml deleted file mode 100644 index e1655f27b977..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ro/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experiment cu bară de navigare subțire"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ru/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ru/strings.xml deleted file mode 100644 index cac66dc921b1..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ru/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Узкая панель навигации (эксперимент)"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sk/strings.xml deleted file mode 100644 index 6a1ce4128cd9..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sk/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Úzky navigačný panel (experiment)"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sl/strings.xml deleted file mode 100644 index beab7b67f60e..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sl/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Preizkus z vitko vrstico za krmarjenje"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sq/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sq/strings.xml deleted file mode 100644 index b7a28d57391d..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sq/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperimenti i shiritit të hollë të navigimit"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sr/strings.xml deleted file mode 100644 index 048f649ddb7b..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sr/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Експеримент са танком траком за навигацију"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sv/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sv/strings.xml deleted file mode 100644 index b94438f6971a..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sv/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experimentellt tunt navigeringsfält"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sw/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sw/strings.xml deleted file mode 100644 index 3a5a73cd0afa..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-sw/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Jaribio la Sehemu ya Viungo Muhimu Inayoweza Kupunguzwa"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-th/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-th/strings.xml deleted file mode 100644 index 945297bec53d..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-th/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"การทดสอบแถบนำทางแบบบาง"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-tl/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-tl/strings.xml deleted file mode 100644 index 0c8087c68b8b..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-tl/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperimentong Slim na Navigation Bar"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-tr/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-tr/strings.xml deleted file mode 100644 index a3ca75455391..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-tr/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"İnce Gezinme Çubuğu Denemesi"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-uk/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-uk/strings.xml deleted file mode 100644 index 656c4a97bd6c..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-uk/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Експеримент із тонкою панеллю навігації"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ur/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ur/strings.xml deleted file mode 100644 index bcd6bc3f257e..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-ur/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"سلم نیویگیشن بار کا تجربہ"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-uz/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-uz/strings.xml deleted file mode 100644 index 0d40981784c8..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-uz/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Ingichka navigatsiya paneli tajribasi"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-vi/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-vi/strings.xml deleted file mode 100644 index dad56b41ae39..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-vi/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Thử nghiệm thanh điều hướng mỏng"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rCN/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rCN/strings.xml deleted file mode 100644 index b2602e0e0093..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"精简导航栏实验"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rTW/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rTW/strings.xml deleted file mode 100644 index 6586a573b494..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"細長導覽列實驗"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zu/strings.xml b/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zu/strings.xml deleted file mode 100644 index 12d04d3b21f6..000000000000 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-zu/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --> - -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Ukuhlolwa kwebha yokuzula encane"</string> -</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-am/strings.xml b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rXC/strings.xml index 6a7d644ff432..ca8c4fab6d00 100644 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-am/strings.xml +++ b/packages/overlays/ExperimentNavigationBarSlim32Overlay/res/values-en-rXC/strings.xml @@ -19,5 +19,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"ቀጭን የአሰሳ አሞሌ ሙከራ"</string> + <string name="experiment_navigationbar_overlay" msgid="2616250866244714325">"Slim Navigation Bar Experiment (32dp)"</string> </resources> diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-af/strings.xml b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rXC/strings.xml index 21a0003bdaa7..9209c2be16d1 100644 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-af/strings.xml +++ b/packages/overlays/ExperimentNavigationBarSlim40Overlay/res/values-en-rXC/strings.xml @@ -19,5 +19,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperiment met dun navigasiebalk"</string> + <string name="experiment_navigationbar_overlay" msgid="6415947279345789008">"Slim Navigation Bar Experiment (40dp)"</string> </resources> diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index 3e07d12b9a99..2d3064017719 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -6604,6 +6604,39 @@ message MetricsEvent { // OS: Q NOTIFICATION_DIRECT_REPLY_ACTION = 1590; + // OPEN: Settings > Developer options > Disable > Info dialog + // CATEGORY: SETTINGS + // OS: Q + DIALOG_DISABLE_DEVELOPMENT_OPTIONS = 1591; + + // Tag for an ACTION: QS -> Tile click / Secondary click / long press + // indicating the StatusBarState when menu was pulled down + // CATEGORY: QUICK_SETTINGS + // OS: Q + FIELD_STATUS_BAR_STATE = 1592; + + // Tag for an ACTION: QS -> Tile click / Secondary click / long press + // indicating whether current state is full QS + // CATEGORY: QUICK_SETTINGS + // OS: Q + FIELD_IS_FULL_QS = 1593; + + // ACTION: Display folding state was changed + // CATEGORY: OTHER + // SUBTYPE: 1 if display is folded, 0 if not. + // OS: Q + ACTION_DISPLAY_FOLD = 1594; + + // OPEN: WifiDppConfiguratorActivity (android.settings.WIFI_DPP_CONFIGURATOR_XXX action intents) + // CATEGORY: SETTINGS + // OS: Q + SETTINGS_WIFI_DPP_CONFIGURATOR = 1595; + + // OPEN: WifiDppEnrolleeActivity (android.settings.WIFI_DPP_ENROLLEE_XXX action intents) + // CATEGORY: SETTINGS + // OS: Q + SETTINGS_WIFI_DPP_ENROLLEE = 1596; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index fba639c3fc4a..121267675329 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -216,6 +216,10 @@ message SystemMessage { // Package: android NOTE_SOFTAP_CONFIG_CHANGED = 50; + // Notify the user that connected to app suggested network. + // Package: android + NOTE_CONNECTED_TO_NETWORK_SUGGESTION = 51; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto index 1fda07499a33..e9ce737eb5a5 100644 --- a/proto/src/wifi.proto +++ b/proto/src/wifi.proto @@ -1344,6 +1344,27 @@ message WifiPowerStats { // Amount of time wifi is in tx (ms) optional int64 tx_time_ms = 5; + + // Amount of time kernel is active because of wifi data (ms) + optional int64 wifi_kernel_active_time_ms = 6; + + // Number of packets sent (tx) + optional int64 num_packets_tx = 7; + + // Number of bytes sent (tx) + optional int64 num_bytes_tx = 8; + + // Number of packets received (rx) + optional int64 num_packets_rx = 9; + + // Number of bytes sent (rx) + optional int64 num_bytes_rx = 10; + + // Amount of time wifi is in sleep (ms) + optional int64 sleep_time_ms = 11; + + // Amount of time wifi is scanning (ms) + optional int64 scan_time_ms = 12; } // Metrics for Wifi Wake diff --git a/services/Android.bp b/services/Android.bp index 33904383f84a..58a09977596f 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -21,9 +21,9 @@ java_library { "services.autofill", "services.backup", "services.companion", + "services.contentcapture", "services.coverage", "services.devicepolicy", - "services.intelligence", "services.midi", "services.net", "services.print", diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index d5deccef412d..36ca52a6e600 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2466,6 +2466,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** + * AIDL-exposed method. System only. + * Gets accessibility window id from window token. + * + * @param windowToken Window token to get accessibility window id. + * @return Accessibility window id for the window token. Returns -1 if no such token is + * registered. + */ + @Override + public int getAccessibilityWindowId(IBinder windowToken) { + synchronized (mLock) { + if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) { + throw new SecurityException("Only SYSTEM can call getAccessibilityWindowId"); + } + + return findWindowIdLocked(windowToken); + } + } + + /** * Get the recommended timeout of interactive controls and non-interactive controls. * * @return A long for pair of {@code int}s. First integer for interactive one, and second diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 944ee3390150..612c9294f5d1 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -74,7 +74,6 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.autofill.ui.AutoFillUI; import com.android.server.infra.AbstractMasterSystemService; -import com.android.server.intelligence.IntelligenceManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -100,6 +99,7 @@ public final class AutofillManagerService private static final Object sLock = AutofillManagerService.class; + private static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes /** * IME supports Smart Suggestions. @@ -155,7 +155,7 @@ public final class AutofillManagerService private final AutofillCompatState mAutofillCompatState = new AutofillCompatState(); private final LocalService mLocalService = new LocalService(); - final IntelligenceManagerInternal mIntelligenceManagerInternal; + private final ActivityManagerInternal mAm; private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -184,7 +184,7 @@ public final class AutofillManagerService public AutofillManagerService(Context context) { super(context, UserManager.DISALLOW_AUTOFILL); mUi = new AutoFillUI(ActivityThread.currentActivityThread().getSystemUiContext()); - mIntelligenceManagerInternal = LocalServices.getService(IntelligenceManagerInternal.class); + mAm = LocalServices.getService(ActivityManagerInternal.class); setLogLevelFromSettings(); setMaxPartitionsFromSettings(); @@ -288,6 +288,11 @@ public final class AutofillManagerService return mSupportedSmartSuggestionModes; } + // Called by AutofillManagerServiceImpl, doesn't need to check permission + boolean isInstantServiceAllowed() { + return mAllowInstantService; + } + // Called by Shell command. void destroySessions(@UserIdInt int userId, IResultReceiver receiver) { Slog.i(TAG, "destroySessions() for userId " + userId); @@ -479,15 +484,15 @@ public final class AutofillManagerService } // Called by Shell command. - void getScore(@Nullable String algorithmName, @NonNull String value1, + void calculateScore(@Nullable String algorithmName, @NonNull String value1, @NonNull String value2, @NonNull RemoteCallback callback) { enforceCallingPermissionForManagement(); final FieldClassificationStrategy strategy = new FieldClassificationStrategy(getContext(), UserHandle.USER_CURRENT); - strategy.getScores(callback, algorithmName, null, - Arrays.asList(AutofillValue.forText(value1)), new String[] { value2 }); + strategy.calculateScores(callback, Arrays.asList(AutofillValue.forText(value1)), + new String[] { value2 }, null, algorithmName, null, null, null); } // Called by Shell command. @@ -502,6 +507,39 @@ public final class AutofillManagerService sFullScreenMode = mode; } + // Called by Shell command. + void setTemporaryAugmentedAutofillService(@UserIdInt int userId, @NonNull String serviceName, + int durationMs) { + Slog.i(mTag, "setTemporaryAugmentedAutofillService(" + userId + ") to " + serviceName + + " for " + durationMs + "ms"); + enforceCallingPermissionForManagement(); + + Preconditions.checkNotNull(serviceName); + if (durationMs > MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS) { + throw new IllegalArgumentException("Max duration is " + + MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS + " (called with " + durationMs + ")"); + } + + synchronized (mLock) { + final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); + if (service != null) { + service.mAugmentedAutofillResolver.setTemporaryServiceLocked(serviceName, + durationMs); + } + } + } + + // Called by Shell command + void resetTemporaryAugmentedAutofillService(@UserIdInt int userId) { + enforceCallingPermissionForManagement(); + synchronized (mLock) { + final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); + if (service != null) { + service.mAugmentedAutofillResolver.resetTemporaryServiceLocked(); + } + } + } + private void setLoggingLevelsLocked(boolean debug, boolean verbose) { com.android.server.autofill.Helper.sDebug = debug; android.view.autofill.Helper.sDebug = debug; @@ -877,14 +915,9 @@ public final class AutofillManagerService throw new IllegalArgumentException(packageName + " is not a valid package", e); } - // TODO(b/113281366): rather than always call AM here, call it on demand on - // getPreviousSessionsLocked()? That way we save space / time here, and don't set - // a callback on AM unnecessarily (see TODO below :-) - final ActivityManagerInternal am = LocalServices - .getService(ActivityManagerInternal.class); // TODO(b/113281366): add a callback method on AM to be notified when a task is finished // so we can clean up sessions kept alive - final int taskId = am.getTaskIdForActivity(activityToken, false); + final int taskId = mAm.getTaskIdForActivity(activityToken, false); final int sessionId; synchronized (mLock) { final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 3945c23eabc7..fa62ef829b32 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -73,8 +73,10 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.LocalServices; import com.android.server.autofill.AutofillManagerService.AutofillCompatState; import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode; +import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks; import com.android.server.autofill.ui.AutoFillUI; import com.android.server.infra.AbstractPerUserSystemService; +import com.android.server.infra.FrameworkResourcesServiceNameResolver; import com.android.server.infra.SecureSettingsServiceNameResolver; import java.io.PrintWriter; @@ -152,6 +154,19 @@ final class AutofillManagerServiceImpl /** When was {@link PruneTask} last executed? */ private long mLastPrune = 0; + /** + * Object used to set the name of the augmented autofill service. + */ + @NonNull + final FrameworkResourcesServiceNameResolver mAugmentedAutofillResolver; + + /** + * Reference to the {@link RemoteAugmentedAutofillService}, is set on demand. + */ + @GuardedBy("mLock") + @Nullable + private RemoteAugmentedAutofillService mRemoteAugmentedAutofillService; + AutofillManagerServiceImpl(AutofillManagerService master, Object lock, LocalLog requestsHistory, LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui, AutofillCompatState autofillCompatState, boolean disabled) { @@ -164,6 +179,12 @@ final class AutofillManagerServiceImpl mUi = ui; mFieldClassificationStrategy = new FieldClassificationStrategy(getContext(), userId); mAutofillCompatState = autofillCompatState; + + mAugmentedAutofillResolver = new FrameworkResourcesServiceNameResolver(master.getContext(), + userId, lock, com.android.internal.R.string.config_defaultAugmentedAutofillService); + mAugmentedAutofillResolver.setOnTemporaryServiceNameChangedCallback( + () -> updateRemoteAugmentedAutofillService()); + updateLocked(disabled); } @@ -851,6 +872,12 @@ final class AutofillManagerServiceImpl } pw.print(prefix); pw.print("Default component: "); pw.println(getContext() .getString(R.string.config_defaultAutofillService)); + pw.print(prefix); pw.print("mAugmentedAutofillNamer: "); + mAugmentedAutofillResolver.dumpShortLocked(pw); pw.println(); + if (mRemoteAugmentedAutofillService != null) { + pw.print(prefix); pw.println("RemoteAugmentedAutofillService: "); + mRemoteAugmentedAutofillService.dump(prefix2, pw); + } pw.print(prefix); pw.print("Field classification enabled: "); pw.println(isFieldClassificationEnabledLocked()); pw.print(prefix); pw.print("Compat pkgs: "); @@ -992,6 +1019,50 @@ final class AutofillManagerServiceImpl return null; } + @GuardedBy("mLock") + @Nullable RemoteAugmentedAutofillService getRemoteAugmentedAutofillServiceLocked() { + if (mRemoteAugmentedAutofillService == null) { + final String serviceName = mAugmentedAutofillResolver.getServiceNameLocked(); + if (serviceName == null) { + if (mMaster.verbose) { + Slog.v(TAG, "getRemoteAugmentedAutofillServiceLocked(): not set"); + } + return null; + } + final ComponentName componentName = RemoteAugmentedAutofillService.getComponentName( + serviceName, mUserId, mAugmentedAutofillResolver.isTemporaryLocked()); + if (componentName == null) return null; + if (sVerbose) { + Slog.v(TAG, "getRemoteAugmentedAutofillServiceLocked(): " + componentName); + } + + mRemoteAugmentedAutofillService = new RemoteAugmentedAutofillService(getContext(), + componentName, mUserId, new RemoteAugmentedAutofillServiceCallbacks() { + @Override + public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) { + // TODO(b/111330312): properly implement + Slog.w(TAG, "remote augmented autofill service died"); + } + }, mMaster.isInstantServiceAllowed(), mMaster.verbose); + } + + return mRemoteAugmentedAutofillService; + } + + /** + * Called when the {@link #mAugmentedAutofillResolver} changed (among other places). + */ + private void updateRemoteAugmentedAutofillService() { + final String serviceName = mAugmentedAutofillResolver.getServiceNameLocked(); + if (serviceName == null) { + if (sVerbose) Slog.v(TAG, "updateRemoteAugmentedAutofillService(): time's up!"); + if (mRemoteAugmentedAutofillService != null) { + mRemoteAugmentedAutofillService.destroy(); + mRemoteAugmentedAutofillService = null; + } + } + } + private void sendStateToClients(boolean resetClient) { final RemoteCallbackList<IAutoFillManagerClient> clients; final int userClientCount; diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java index 522280e4690b..c562fb1b7dde 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java @@ -104,6 +104,11 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { pw.println(" set bind-instant-service-allowed [true | false]"); pw.println(" Sets whether binding to services provided by instant apps is allowed"); pw.println(""); + pw.println(" set temporary-augmented-service USER_ID [COMPONENT_NAME DURATION]"); + pw.println(" Temporarily (for DURATION ms) changes the augmented autofill service " + + "implementation."); + pw.println(" To reset, call with just the USER_ID argument."); + pw.println(""); pw.println(" list sessions [--user USER_ID]"); pw.println(" Lists all pending sessions."); pw.println(""); @@ -151,6 +156,8 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { return setFullScreenMode(pw); case "bind-instant-service-allowed": return setBindInstantService(pw); + case "temporary-augmented-service": + return setTemporaryAugmentedService(pw); default: pw.println("Invalid set: " + what); return -1; @@ -226,7 +233,7 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { final String value2 = getNextArgRequired(); final CountDownLatch latch = new CountDownLatch(1); - mService.getScore(algorithm, value1, value2, new RemoteCallback((result) -> { + mService.calculateScore(algorithm, value1, value2, new RemoteCallback((result) -> { final Scores scores = result.getParcelable(EXTRA_SCORES); if (scores == null) { pw.println("no score"); @@ -293,6 +300,20 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { } } + private int setTemporaryAugmentedService(PrintWriter pw) { + final int userId = getNextIntArgRequired(); + final String serviceName = getNextArg(); + if (serviceName == null) { + mService.resetTemporaryAugmentedAutofillService(userId); + return 0; + } + final int duration = getNextIntArgRequired(); + mService.setTemporaryAugmentedAutofillService(userId, serviceName, duration); + pw.println("AugmentedAutofillService temporarily set to " + serviceName + " for " + + duration + "ms"); + return 0; + } + private int requestDestroy(PrintWriter pw) { if (!isNextArgSessions(pw)) { return -1; @@ -371,4 +392,8 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { } return UserHandle.USER_ALL; } + + private int getNextIntArgRequired() { + return Integer.parseInt(getNextArgRequired()); + } } diff --git a/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java index 293f908e2708..9db6254a8baa 100644 --- a/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java +++ b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java @@ -15,11 +15,12 @@ */ package com.android.server.autofill; -import static com.android.server.autofill.Helper.sDebug; -import static com.android.server.autofill.Helper.sVerbose; import static android.service.autofill.AutofillFieldClassificationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS; import static android.service.autofill.AutofillFieldClassificationService.SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM; +import static com.android.server.autofill.Helper.sDebug; +import static com.android.server.autofill.Helper.sVerbose; + import android.Manifest; import android.annotation.MainThread; import android.annotation.NonNull; @@ -40,6 +41,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.service.autofill.AutofillFieldClassificationService; import android.service.autofill.IAutofillFieldClassificationService; +import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.view.autofill.AutofillValue; @@ -257,13 +259,13 @@ final class FieldClassificationStrategy { return parser.get(res, resourceId); } - //TODO(b/70291841): rename this method (and all others in the chain) to something like - // calculateScores() ? - void getScores(RemoteCallback callback, @Nullable String algorithmName, - @Nullable Bundle algorithmArgs, @NonNull List<AutofillValue> actualValues, - @NonNull String[] userDataValues) { - connectAndRun((service) -> service.getScores(callback, algorithmName, - algorithmArgs, actualValues, userDataValues)); + void calculateScores(RemoteCallback callback, @NonNull List<AutofillValue> actualValues, + @NonNull String[] userDataValues, @NonNull String[] categoryIds, + @Nullable String defaultAlgorithm, @Nullable Bundle defaultArgs, + @Nullable ArrayMap<String, String> algorithms, + @Nullable ArrayMap<String, Bundle> args) { + connectAndRun((service) -> service.calculateScores(callback, actualValues, + userDataValues, categoryIds, defaultAlgorithm, defaultArgs, algorithms, args)); } void dump(String prefix, PrintWriter pw) { diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java new file mode 100644 index 000000000000..fc7265db1be2 --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.AppGlobals; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.service.autofill.augmented.AugmentedAutofillService; +import android.service.autofill.augmented.IAugmentedAutofillService; +import android.service.autofill.augmented.IFillCallback; +import android.text.format.DateUtils; +import android.util.Slog; +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillValue; +import android.view.autofill.IAutoFillManagerClient; + +import com.android.internal.os.IResultReceiver; +import com.android.server.infra.AbstractSinglePendingRequestRemoteService; + +final class RemoteAugmentedAutofillService + extends AbstractSinglePendingRequestRemoteService<RemoteAugmentedAutofillService, + IAugmentedAutofillService> { + + private static final String TAG = RemoteAugmentedAutofillService.class.getSimpleName(); + + // TODO(b/117779333): changed it so it's permanentely bound + private static final long TIMEOUT_IDLE_BIND_MILLIS = 2 * DateUtils.MINUTE_IN_MILLIS; + private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS; + + RemoteAugmentedAutofillService(Context context, ComponentName serviceName, + int userId, RemoteAugmentedAutofillServiceCallbacks callbacks, + boolean bindInstantServiceAllowed, boolean verbose) { + super(context, AugmentedAutofillService.SERVICE_INTERFACE, serviceName, userId, callbacks, + bindInstantServiceAllowed, verbose); + } + + @Nullable + public static ComponentName getComponentName(@NonNull String componentName, + @UserIdInt int userId, boolean isTemporary) { + int flags = PackageManager.GET_META_DATA; + if (!isTemporary) { + flags |= PackageManager.MATCH_SYSTEM_ONLY; + } + + final ComponentName serviceComponent; + ServiceInfo serviceInfo = null; + try { + serviceComponent = ComponentName.unflattenFromString(componentName); + serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, flags, + userId); + if (serviceInfo == null) { + Slog.e(TAG, "Bad service name for flags " + flags + ": " + componentName); + return null; + } + } catch (Exception e) { + Slog.e(TAG, "Error getting service info for '" + componentName + "': " + e); + return null; + } + return serviceComponent; + } + + @Override // from AbstractRemoteService + protected IAugmentedAutofillService getServiceInterface(IBinder service) { + return IAugmentedAutofillService.Stub.asInterface(service); + } + + @Override // from AbstractRemoteService + protected long getTimeoutIdleBindMillis() { + return TIMEOUT_IDLE_BIND_MILLIS; + } + + @Override // from AbstractRemoteService + protected long getRemoteRequestMillis() { + return TIMEOUT_REMOTE_REQUEST_MILLIS; + } + + /** + * Called by {@link Session} to request augmented autofill. + */ + public void onRequestAutofillLocked(int sessionId, @NonNull IAutoFillManagerClient client, + int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId, + @Nullable AutofillValue focusedValue) { + scheduleRequest(new PendingAutofillRequest(this, sessionId, client, taskId, + activityComponent, focusedId, focusedValue)); + } + + /** + * Called by {@link Session} when it's time to destroy all augmented autofill requests. + */ + public void onDestroyAutofillWindowsRequest(int sessionId) { + scheduleAsyncRequest((s) -> s.onDestroyFillWindowRequest(sessionId)); + } + + // TODO(b/111330312): inline into PendingAutofillRequest if it doesn't have any other subclass + private abstract static class MyPendingRequest + extends PendingRequest<RemoteAugmentedAutofillService, IAugmentedAutofillService> { + protected final int mSessionId; + + private MyPendingRequest(@NonNull RemoteAugmentedAutofillService service, int sessionId) { + super(service); + mSessionId = sessionId; + } + } + + private static final class PendingAutofillRequest extends MyPendingRequest { + private final @NonNull AutofillId mFocusedId; + private final @Nullable AutofillValue mFocusedValue; + private final @NonNull IAutoFillManagerClient mClient; + private final @NonNull ComponentName mActivityComponent; + private final int mTaskId; + private final long mRequestTime = SystemClock.elapsedRealtime(); + private final @NonNull IFillCallback mCallback; + + protected PendingAutofillRequest(@NonNull RemoteAugmentedAutofillService service, + int sessionId, @NonNull IAutoFillManagerClient client, int taskId, + @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId, + @Nullable AutofillValue focusedValue) { + super(service, sessionId); + mClient = client; + mTaskId = taskId; + mActivityComponent = activityComponent; + mFocusedId = focusedId; + mFocusedValue = focusedValue; + mCallback = new IFillCallback.Stub() { + @Override + public void onSuccess() { + if (!finish()) return; + // NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks + } + }; + } + + @Override + public void run() { + final RemoteAugmentedAutofillService remoteService = getService(); + if (remoteService == null) return; + + final IResultReceiver receiver = new IResultReceiver.Stub() { + + @Override + public void send(int resultCode, Bundle resultData) throws RemoteException { + final IBinder realClient = resultData + .getBinder(AutofillManager.EXTRA_AUGMENTED_AUTOFILL_CLIENT); + remoteService.mService.onFillRequest(mSessionId, realClient, mTaskId, + mActivityComponent, mFocusedId, mFocusedValue, mRequestTime, mCallback); + } + }; + + // TODO(b/111330312): set cancellation signal, timeout (from both mClient and service), + // cache IAugmentedAutofillManagerClient reference, etc... + try { + mClient.getAugmentedAutofillClient(receiver); + } catch (RemoteException e) { + Slog.e(TAG, "exception handling getAugmentedAutofillClient() for " + + mSessionId + ": " + e); + finish(); + } + } + + @Override + protected void onTimeout(RemoteAugmentedAutofillService remoteService) { + Slog.wtf(TAG, "timed out: " + this); + // NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks + finish(); + } + + } + + public interface RemoteAugmentedAutofillServiceCallbacks + extends VultureCallback<RemoteAugmentedAutofillService> { + // NOTE: so far we don't need to notify the callback implementation (an inner class on + // AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this + // callback interface is empty. + } +} diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 4b7d29025730..417ea9c84393 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -28,7 +28,6 @@ import android.content.Context; import android.content.IntentSender; import android.os.IBinder; import android.os.ICancellationSignal; -import android.os.IInterface; import android.os.RemoteException; import android.service.autofill.AutofillService; import android.service.autofill.FillRequest; @@ -42,15 +41,15 @@ import android.util.Slog; import com.android.server.infra.AbstractSinglePendingRequestRemoteService; -final class RemoteFillService extends AbstractSinglePendingRequestRemoteService<RemoteFillService> { +final class RemoteFillService + extends AbstractSinglePendingRequestRemoteService<RemoteFillService, IAutoFillService> { private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; private final FillServiceCallbacks mCallbacks; - private IAutoFillService mAutoFillService; - public interface FillServiceCallbacks extends VultureCallback { + public interface FillServiceCallbacks extends VultureCallback<RemoteFillService> { void onFillRequestSuccess(int requestId, @Nullable FillResponse response, @NonNull String servicePackageName, int requestFlags); void onFillRequestFailure(int requestId, @Nullable CharSequence message); @@ -71,21 +70,20 @@ final class RemoteFillService extends AbstractSinglePendingRequestRemoteService< @Override // from AbstractRemoteService protected void handleOnConnectedStateChanged(boolean state) { - if (mAutoFillService == null) { + if (mService == null) { Slog.w(mTag, "onConnectedStateChanged(): null service"); return; } try { - mAutoFillService.onConnectedStateChanged(state); + mService.onConnectedStateChanged(state); } catch (Exception e) { Slog.w(mTag, "Exception calling onConnectedStateChanged(): " + e); } } @Override // from AbstractRemoteService - protected IInterface getServiceInterface(IBinder service) { - mAutoFillService = IAutoFillService.Stub.asInterface(service); - return mAutoFillService; + protected IAutoFillService getServiceInterface(IBinder service) { + return IAutoFillService.Stub.asInterface(service); } @Override // from AbstractRemoteService @@ -127,17 +125,15 @@ final class RemoteFillService extends AbstractSinglePendingRequestRemoteService< } public void onFillRequest(@NonNull FillRequest request) { - cancelScheduledUnbind(); scheduleRequest(new PendingFillRequest(request, this)); } public void onSaveRequest(@NonNull SaveRequest request) { - cancelScheduledUnbind(); scheduleRequest(new PendingSaveRequest(request, this)); } private boolean handleResponseCallbackCommon( - @NonNull PendingRequest<RemoteFillService> pendingRequest) { + @NonNull PendingRequest<RemoteFillService, IAutoFillService> pendingRequest) { if (isDestroyed()) return false; if (mPendingRequest == pendingRequest) { @@ -204,7 +200,8 @@ final class RemoteFillService extends AbstractSinglePendingRequestRemoteService< }); } - private static final class PendingFillRequest extends PendingRequest<RemoteFillService> { + private static final class PendingFillRequest + extends PendingRequest<RemoteFillService, IAutoFillService> { private final FillRequest mRequest; private final IFillCallback mCallback; private ICancellationSignal mCancellation; @@ -282,7 +279,7 @@ final class RemoteFillService extends AbstractSinglePendingRequestRemoteService< if (remoteService != null) { if (sVerbose) Slog.v(mTag, "calling onFillRequest() for id=" + mRequest.getId()); try { - remoteService.mAutoFillService.onFillRequest(mRequest, mCallback); + remoteService.mService.onFillRequest(mRequest, mCallback); } catch (RemoteException e) { Slog.e(mTag, "Error calling on fill request", e); @@ -310,7 +307,8 @@ final class RemoteFillService extends AbstractSinglePendingRequestRemoteService< } } - private static final class PendingSaveRequest extends PendingRequest<RemoteFillService> { + private static final class PendingSaveRequest + extends PendingRequest<RemoteFillService, IAutoFillService> { private final SaveRequest mRequest; private final ISaveCallback mCallback; @@ -355,7 +353,7 @@ final class RemoteFillService extends AbstractSinglePendingRequestRemoteService< if (remoteService != null) { if (sVerbose) Slog.v(mTag, "calling onSaveRequest()"); try { - remoteService.mAutoFillService.onSaveRequest(mRequest, mCallback); + remoteService.mService.onSaveRequest(mRequest, mCallback); } catch (RemoteException e) { Slog.e(mTag, "Error calling on save request", e); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index fb64cb28619d..0e9b407372e0 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -98,9 +98,6 @@ import com.android.internal.util.ArrayUtils; import com.android.server.autofill.AutofillManagerService.SmartSuggestionMode; import com.android.server.autofill.ui.AutoFillUI; import com.android.server.autofill.ui.PendingUi; -import com.android.server.infra.AbstractRemoteService; -import com.android.server.intelligence.IntelligenceManagerInternal; -import com.android.server.intelligence.IntelligenceManagerInternal.AugmentedAutofillCallback; import java.io.PrintWriter; import java.util.ArrayList; @@ -248,9 +245,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private final SparseArray<LogMaker> mRequestLogs = new SparseArray<>(1); + /** + * Destroys the augmented Autofill UI. + */ + // TODO(b/111330312): this runnable is called when the Autofill session is destroyed, the + // main reason being the cases where user tap HOME. + // Right now it's completely destroying the UI, but we need to decide whether / how to + // properly recover it later (for example, if the user switches back to the activity, + // should it be restored? Right not it kind of is, because Autofill's Session trigger a + // new FillRequest, which in turn triggers the Augmented Autofill request again) @GuardedBy("mLock") @Nullable - private AugmentedAutofillCallback mAugmentedAutofillCallback; + private Runnable mAugmentedAutofillDestroyer; /** * Receiver of assist data from the app's {@link Activity}. @@ -902,7 +908,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // VultureCallback @Override - public void onServiceDied(AbstractRemoteService<? extends AbstractRemoteService<?>> service) { + public void onServiceDied(@NonNull RemoteFillService service) { Slog.w(TAG, "removing session because service died"); forceRemoveSelfLocked(); } @@ -1231,7 +1237,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - final UserData userData = mService.getUserData(); + final UserData packageUserData = lastResponse.getUserData(); + + final UserData userData; + if (packageUserData != null) { + // Replace default userData + userData = packageUserData; + } else { + userData = mService.getUserData(); + } for (int i = 0; i < mViewStates.size(); i++) { final ViewState viewState = mViewStates.valueAt(i); @@ -1387,6 +1401,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final String[] userValues = userData.getValues(); final String[] categoryIds = userData.getCategoryIds(); + final String defaultAlgorithm = userData.getFieldClassificationAlgorithm(); + final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs(); + + final ArrayMap<String, String> algorithms = userData.getFieldClassificationAlgorithms(); + final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs(); + // Sanity check if (userValues == null || categoryIds == null || userValues.length != categoryIds.length) { final int valuesLength = userValues == null ? -1 : userValues.length; @@ -1402,8 +1422,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<FieldClassification> detectedFieldClassifications = new ArrayList<>( maxFieldsSize); - final String algorithm = userData.getFieldClassificationAlgorithm(); - final Bundle algorithmArgs = userData.getAlgorithmArgs(); final int viewsSize = viewStates.size(); // First, we get all scores. @@ -1490,7 +1508,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mComponentName, mCompatMode); }); - fcStrategy.getScores(callback, algorithm, algorithmArgs, currentValues, userValues); + fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds, + defaultAlgorithm, defaultArgs, algorithms, args); } /** @@ -2516,8 +2535,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // The default autofill service cannot fullfill the request, let's check if the intelligence // service can. - mAugmentedAutofillCallback = triggerAugmentedAutofillLocked(); - if (mAugmentedAutofillCallback == null) { + mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked(); + if (mAugmentedAutofillDestroyer == null) { if (sVerbose) { Slog.v(TAG, "canceling session " + id + " when server returned null and there is no" + " AugmentedAutofill for user"); @@ -2539,21 +2558,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Tries to trigger Augmented Autofill when the standard service could not fulfill a request. * - * @return callback to the Augmented Autofill service, or {@code null} if not supported. + * @return callback to destroy the autofill UI, or {@code null} if not supported. */ // TODO(b/111330312): might need to call it in other places, like when the service returns a // non-null response but without datasets (for example, just SaveInfo) @GuardedBy("mLock") - private AugmentedAutofillCallback triggerAugmentedAutofillLocked() { + private Runnable triggerAugmentedAutofillLocked() { // Check if Smart Suggestions is supported... final @SmartSuggestionMode int supportedModes = mService .getSupportedSmartSuggestionModesLocked(); if (supportedModes == 0) return null; // ...then if the service is set for the user - final IntelligenceManagerInternal intelligenceManagerInternal = mService - .getMaster().mIntelligenceManagerInternal; - if (intelligenceManagerInternal == null) return null; + + final RemoteAugmentedAutofillService remoteService = mService + .getRemoteAugmentedAutofillServiceLocked(); + if (remoteService == null) return null; // Define which mode will be used final int mode; @@ -2584,8 +2604,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // TODO(b/111330312): we might need to add a new state in the AutofillManager to optimize // furgher AFM -> AFMS calls. // TODO(b/119638958): add CTS tests - return intelligenceManagerInternal.requestAutofill(mService.getUserId(), mClient, - mActivityToken, this.id, mCurrentViewId, currentValue); + remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, mCurrentViewId, + currentValue); + + if (mAugmentedAutofillDestroyer == null) { + mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest(id); + } + return mAugmentedAutofillDestroyer; } @GuardedBy("mLock") @@ -2866,8 +2891,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState pw.print(prefix); pw.print("mSaveOnAllViewsInvisible: "); pw.println( mSaveOnAllViewsInvisible); pw.print(prefix); pw.print("mSelectedDatasetIds: "); pw.println(mSelectedDatasetIds); - if (mAugmentedAutofillCallback != null) { - pw.print(prefix); pw.println("has AugmentedAutofillCallback"); + if (mAugmentedAutofillDestroyer != null) { + pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer"); } mRemoteFillService.dump(prefix, pw); } @@ -3045,8 +3070,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void destroyAugmentedAutofillWindowsLocked() { - if (mAugmentedAutofillCallback != null) { - mAugmentedAutofillCallback.destroy(); + if (mAugmentedAutofillDestroyer != null) { + mAugmentedAutofillDestroyer.run(); + mAugmentedAutofillDestroyer = null; } } diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 0b06f286441a..a917ced2ae68 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -16,7 +16,9 @@ package com.android.server.backup; +import android.Manifest; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; @@ -27,10 +29,10 @@ import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.os.Binder; import android.os.Environment; import android.os.HandlerThread; import android.os.IBinder; @@ -38,8 +40,6 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.Trace; import android.os.UserHandle; -import android.provider.Settings; -import android.text.TextUtils; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -82,11 +82,12 @@ public class BackupManagerService { return sInstance; } - /** Helper to create the {@link BackupManagerService} instance. */ - public static BackupManagerService create( - Context context, - Trampoline parent, - HandlerThread backupThread) { + private final Context mContext; + private UserBackupManagerService mUserBackupManagerService; + + /** Instantiate a new instance of {@link BackupManagerService}. */ + public BackupManagerService( + Context context, Trampoline trampoline, HandlerThread backupThread) { // Set up our transport options and initialize the default transport SystemConfig systemConfig = SystemConfig.getInstance(); Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist(); @@ -94,50 +95,24 @@ public class BackupManagerService { transportWhitelist = Collections.emptySet(); } - String transport = - Settings.Secure.getString( - context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT); - if (TextUtils.isEmpty(transport)) { - transport = null; - } - if (DEBUG) { - Slog.v(TAG, "Starting with transport " + transport); - } - TransportManager transportManager = - new TransportManager( - context, - transportWhitelist, - transport); - - // If encrypted file systems is enabled or disabled, this call will return the - // correct directory. - File baseStateDir = new File(Environment.getDataDirectory(), "backup"); - - // This dir on /cache is managed directly in init.rc - File dataDir = new File(Environment.getDownloadCacheDirectory(), "backup_stage"); - - return new BackupManagerService( - context, - parent, - backupThread, - baseStateDir, - dataDir, - transportManager); + mContext = context; + mUserBackupManagerService = + UserBackupManagerService.createAndInitializeService( + context, trampoline, backupThread, transportWhitelist); } - private UserBackupManagerService mUserBackupManagerService; - - /** Instantiate a new instance of {@link BackupManagerService}. */ - public BackupManagerService( - Context context, - Trampoline trampoline, - HandlerThread backupThread, - File baseStateDir, - File dataDir, - TransportManager transportManager) { - mUserBackupManagerService = - new UserBackupManagerService( - context, trampoline, backupThread, baseStateDir, dataDir, transportManager); + /** + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id on which the backup operation is being requested. + * @param message A message to include in the exception if it is thrown. + */ + private void enforceCallingPermissionOnUserId(int userId, String message) { + if (Binder.getCallingUserHandle().getIdentifier() != userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); + } } // TODO(b/118520567): Remove when tests are modified to use per-user instance. @@ -151,30 +126,6 @@ public class BackupManagerService { * a background thread to keep the unlock time down. */ public void unlockSystemUser() { - // Migrate legacy setting - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate"); - if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) { - if (DEBUG) { - Slog.i(TAG, "Backup enable apparently not migrated"); - } - ContentResolver resolver = sInstance.getContext().getContentResolver(); - int enableState = Settings.Secure.getIntForUser(resolver, - Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM); - if (enableState >= 0) { - if (DEBUG) { - Slog.i(TAG, "Migrating enable state " + (enableState != 0)); - } - writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM); - Settings.Secure.putStringForUser(resolver, - Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM); - } else { - if (DEBUG) { - Slog.i(TAG, "Backup not yet configured; retaining null enable state"); - } - } - } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable"); try { sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM)); @@ -184,6 +135,15 @@ public class BackupManagerService { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } + /** + * Starts the backup service for user {@code userId} by creating a new instance of {@link + * UserBackupManagerService} and registering it with this service. + */ + // TODO(b/120212806): Add UserBackupManagerService initialization logic. + void startServiceForUser(int userId) { + // Intentionally empty. + } + /* * The following methods are implementations of IBackupManager methods called from Trampoline. * They delegate to the appropriate per-user instance of UserBackupManagerService to perform the @@ -373,7 +333,8 @@ public class BackupManagerService { // --------------------------------------------- /** Enable/disable the backup service. This is user-configurable via backup settings. */ - public void setBackupEnabled(boolean enable) { + public void setBackupEnabled(@UserIdInt int userId, boolean enable) { + enforceCallingPermissionOnUserId(userId, "setBackupEnabled"); mUserBackupManagerService.setBackupEnabled(enable); } @@ -390,7 +351,8 @@ public class BackupManagerService { /** * Return {@code true} if the backup mechanism is currently enabled, else returns {@code false}. */ - public boolean isBackupEnabled() { + public boolean isBackupEnabled(@UserIdInt int userId) { + enforceCallingPermissionOnUserId(userId, "isBackupEnabled"); return mUserBackupManagerService.isBackupEnabled(); } @@ -414,7 +376,8 @@ public class BackupManagerService { * Run a backup pass immediately for any key-value backup applications that have declared that * they have pending updates. */ - public void backupNow() { + public void backupNow(@UserIdInt int userId) { + enforceCallingPermissionOnUserId(userId, "backupNow"); mUserBackupManagerService.backupNow(); } @@ -423,12 +386,18 @@ public class BackupManagerService { * IBackupManagerMonitor} for receiving events during the operation. */ public int requestBackup( - String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor, int flags) { + @UserIdInt int userId, + String[] packages, + IBackupObserver observer, + IBackupManagerMonitor monitor, + int flags) { + enforceCallingPermissionOnUserId(userId, "requestBackup"); return mUserBackupManagerService.requestBackup(packages, observer, monitor, flags); } /** Cancel all running backup operations. */ - public void cancelBackups() { + public void cancelBackups(@UserIdInt int userId) { + enforceCallingPermissionOnUserId(userId, "cancelBackups"); mUserBackupManagerService.cancelBackups(); } @@ -563,12 +532,6 @@ public class BackupManagerService { mUserBackupManagerService.dump(fd, pw, args); } - private static boolean backupSettingMigrated(int userId) { - File base = new File(Environment.getDataDirectory(), "backup"); - File enableFile = new File(base, BACKUP_ENABLE_FILE); - return enableFile.exists(); - } - private static boolean readBackupEnableState(int userId) { File base = new File(Environment.getDataDirectory(), "backup"); File enableFile = new File(base, BACKUP_ENABLE_FILE); @@ -598,14 +561,10 @@ public class BackupManagerService { stage.renameTo(enableFile); // will be synced immediately by the try-with-resources call to close() } catch (IOException | RuntimeException e) { - // Whoops; looks like we're doomed. Roll everything out, disabled, - // including the legacy state. - Slog.e(TAG, "Unable to record backup enable state; reverting to disabled: " - + e.getMessage()); - - ContentResolver resolver = sInstance.getContext().getContentResolver(); - Settings.Secure.putStringForUser(resolver, - Settings.Secure.BACKUP_ENABLED, null, userId); + Slog.e( + TAG, + "Unable to record backup enable state; reverting to disabled: " + + e.getMessage()); enableFile.delete(); stage.delete(); } @@ -626,7 +585,9 @@ public class BackupManagerService { @Override public void onUnlockUser(int userId) { if (userId == UserHandle.USER_SYSTEM) { - sInstance.unlockSystemUser(); + sInstance.initializeServiceAndUnlockSystemUser(); + } else { + sInstance.startServiceForUser(userId); } } } diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index 59629aac7b4d..ed6ff9b1d77d 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -19,6 +19,7 @@ package com.android.server.backup; import static com.android.server.backup.BackupManagerService.TAG; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.admin.DevicePolicyManager; import android.app.backup.BackupManager; import android.app.backup.IBackupManager; @@ -41,6 +42,7 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; +import android.provider.Settings; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -81,6 +83,12 @@ public class Trampoline extends IBackupManager.Stub { // Product-level suppression of backup/restore. private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable"; + private static final String BACKUP_THREAD = "backup"; + + /** Values for setting {@link Settings.Global#BACKUP_MULTI_USER_ENABLED} */ + private static final int MULTI_USER_DISABLED = 0; + private static final int MULTI_USER_ENABLED = 1; + private final Context mContext; @GuardedBy("mStateLock") @@ -91,18 +99,35 @@ public class Trampoline extends IBackupManager.Stub { private volatile BackupManagerService mService; private HandlerThread mHandlerThread; + private Handler mHandler; public Trampoline(Context context) { mContext = context; mGlobalDisable = isBackupDisabled(); mSuppressFile = getSuppressFile(); mSuppressFile.getParentFile().mkdirs(); + + mHandlerThread = new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); } protected boolean isBackupDisabled() { return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false); } + protected boolean isMultiUserEnabled() { + return Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.BACKUP_MULTI_USER_ENABLED, + MULTI_USER_DISABLED) + == MULTI_USER_ENABLED; + } + + protected int binderGetCallingUserId() { + return Binder.getCallingUserHandle().getIdentifier(); + } + protected int binderGetCallingUid() { return Binder.getCallingUid(); } @@ -117,7 +142,7 @@ public class Trampoline extends IBackupManager.Stub { } protected BackupManagerService createBackupManagerService() { - return BackupManagerService.create(mContext, this, mHandlerThread); + return new BackupManagerService(mContext, this, mHandlerThread); } /** @@ -147,15 +172,9 @@ public class Trampoline extends IBackupManager.Stub { /** * Called from {@link BackupManagerService.Lifecycle} when the system user is unlocked. Attempts * to initialize {@link BackupManagerService} and set backup state for the system user. - * - * @see BackupManagerService#unlockSystemUser() */ - void unlockSystemUser() { - mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND); - mHandlerThread.start(); - - Handler h = new Handler(mHandlerThread.getLooper()); - h.post( + void initializeServiceAndUnlockSystemUser() { + mHandler.post( () -> { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init"); initializeService(UserHandle.USER_SYSTEM); @@ -170,6 +189,29 @@ public class Trampoline extends IBackupManager.Stub { } /** + * Called from {@link BackupManagerService.Lifecycle} when a non-system user {@code userId} is + * unlocked. Starts the backup service for this user if the service supports multi-user. + * Offloads work onto the handler thread {@link #mHandlerThread} to keep unlock time low. + */ + // TODO(b/120212806): Consolidate service start for system and non-system users when system + // user-only logic is removed. + void startServiceForUser(int userId) { + if (!isMultiUserEnabled()) { + Slog.i(TAG, "Multi-user disabled, cannot start service for user: " + userId); + return; + } + + mHandler.post( + () -> { + BackupManagerService service = mService; + if (service != null) { + Slog.i(TAG, "Starting service for user: " + userId); + service.startServiceForUser(userId); + } + }); + } + + /** * Only privileged callers should be changing the backup state. This method only acts on {@link * UserHandle#USER_SYSTEM} and is a no-op if passed non-system users. Deactivating backup in the * system user also deactivates backup in all users. @@ -282,14 +324,20 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void setBackupEnabled(boolean isEnabled) throws RemoteException { + public void setBackupEnabledForUser(@UserIdInt int userId, boolean isEnabled) + throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.setBackupEnabled(isEnabled); + svc.setBackupEnabled(userId, isEnabled); } } @Override + public void setBackupEnabled(boolean isEnabled) throws RemoteException { + setBackupEnabledForUser(binderGetCallingUserId(), isEnabled); + } + + @Override public void setAutoRestore(boolean doAutoRestore) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { @@ -306,9 +354,14 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public boolean isBackupEnabled() throws RemoteException { + public boolean isBackupEnabledForUser(@UserIdInt int userId) throws RemoteException { BackupManagerService svc = mService; - return (svc != null) ? svc.isBackupEnabled() : false; + return (svc != null) ? svc.isBackupEnabled(userId) : false; + } + + @Override + public boolean isBackupEnabled() throws RemoteException { + return isBackupEnabledForUser(binderGetCallingUserId()); } @Override @@ -324,14 +377,19 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void backupNow() throws RemoteException { + public void backupNowForUser(@UserIdInt int userId) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.backupNow(); + svc.backupNow(userId); } } @Override + public void backupNow() throws RemoteException { + backupNowForUser(binderGetCallingUserId()); + } + + @Override public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem, boolean doCompress, boolean doKeyValue, String[] packageNames) @@ -506,24 +564,36 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public int requestBackup(String[] packages, IBackupObserver observer, - IBackupManagerMonitor monitor, int flags) throws RemoteException { + public int requestBackupForUser(@UserIdInt int userId, String[] packages, IBackupObserver + observer, IBackupManagerMonitor monitor, int flags) throws RemoteException { BackupManagerService svc = mService; if (svc == null) { return BackupManager.ERROR_BACKUP_NOT_ALLOWED; } - return svc.requestBackup(packages, observer, monitor, flags); + return svc.requestBackup(userId, packages, observer, monitor, flags); } @Override - public void cancelBackups() throws RemoteException { + public int requestBackup(String[] packages, IBackupObserver observer, + IBackupManagerMonitor monitor, int flags) throws RemoteException { + return requestBackupForUser(binderGetCallingUserId(), packages, + observer, monitor, flags); + } + + @Override + public void cancelBackupsForUser(@UserIdInt int userId) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.cancelBackups(); + svc.cancelBackups(userId); } } @Override + public void cancelBackups() throws RemoteException { + cancelBackupsForUser(binderGetCallingUserId()); + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index fe16afe864ac..5220a590ddda 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -18,6 +18,7 @@ package com.android.server.backup; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND; +import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.server.backup.BackupManagerService.DEBUG; import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING; import static com.android.server.backup.BackupManagerService.MORE_DEBUG; @@ -68,6 +69,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -86,6 +88,7 @@ import android.os.WorkSource; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.provider.Settings; +import android.text.TextUtils; import android.util.ArraySet; import android.util.AtomicFile; import android.util.EventLog; @@ -167,6 +170,10 @@ public class UserBackupManagerService { // Persistently track the need to do a full init. private static final String INIT_SENTINEL_FILE_NAME = "_need_init_"; + // Name of the directories the service stores bookkeeping data under. + private static final String BACKUP_PERSISTENT_DIR = "backup"; + private static final String BACKUP_STAGING_DIR = "backup_stage"; + // System-private key used for backing up an app's widget state. Must // begin with U+FFxx by convention (we reserve all keys starting // with U+FF00 or higher for system use). @@ -360,15 +367,71 @@ public class UserBackupManagerService { private long mAncestralToken = 0; private long mCurrentToken = 0; + /** + * Creates an instance of {@link UserBackupManagerService} and initializes state for it. This + * includes setting up the directories where we keep our bookkeeping and transport management. + * + * @see #createAndInitializeService(Context, Trampoline, HandlerThread, File, File, + * TransportManager) + */ + static UserBackupManagerService createAndInitializeService( + Context context, + Trampoline trampoline, + HandlerThread backupThread, + Set<ComponentName> transportWhitelist) { + String currentTransport = + Settings.Secure.getString( + context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT); + if (TextUtils.isEmpty(currentTransport)) { + currentTransport = null; + } + + if (DEBUG) { + Slog.v(TAG, "Starting with transport " + currentTransport); + } + TransportManager transportManager = + new TransportManager(context, transportWhitelist, currentTransport); + + File baseStateDir = new File(Environment.getDataDirectory(), BACKUP_PERSISTENT_DIR); + + // This dir on /cache is managed directly in init.rc + File dataDir = new File(Environment.getDownloadCacheDirectory(), BACKUP_STAGING_DIR); + + return createAndInitializeService( + context, trampoline, backupThread, baseStateDir, dataDir, transportManager); + } + + /** + * Creates an instance of {@link UserBackupManagerService}. + * + * @param context The system server context. + * @param trampoline A reference to the proxy to {@link BackupManagerService}. + * @param backupThread The thread running backup/restore operations for the user. + * @param baseStateDir The directory we store the user's persistent bookkeeping data. + * @param dataDir The directory we store the user's temporary staging data. + * @param transportManager The {@link TransportManager} responsible for handling the user's + * transports. + */ @VisibleForTesting - public UserBackupManagerService( + public static UserBackupManagerService createAndInitializeService( + Context context, + Trampoline trampoline, + HandlerThread backupThread, + File baseStateDir, + File dataDir, + TransportManager transportManager) { + return new UserBackupManagerService( + context, trampoline, backupThread, baseStateDir, dataDir, transportManager); + } + + private UserBackupManagerService( Context context, Trampoline parent, HandlerThread backupThread, File baseStateDir, File dataDir, TransportManager transportManager) { - mContext = context; + mContext = checkNotNull(context, "context cannot be null"); mPackageManager = context.getPackageManager(); mPackageManagerBinder = AppGlobals.getPackageManager(); mActivityManager = ActivityManager.getService(); @@ -377,6 +440,7 @@ public class UserBackupManagerService { mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getService("mount")); + checkNotNull(parent, "trampoline cannot be null"); mBackupManagerBinder = Trampoline.asInterface(parent.asBinder()); mAgentTimeoutParameters = new @@ -384,6 +448,7 @@ public class UserBackupManagerService { mAgentTimeoutParameters.start(); // spin up the backup/restore handler thread + checkNotNull(backupThread, "backupThread cannot be null"); mBackupHandler = new BackupHandler(this, backupThread.getLooper()); // Set up our bookkeeping @@ -398,13 +463,13 @@ public class UserBackupManagerService { Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), false, mProvisionedObserver); - mBaseStateDir = baseStateDir; + mBaseStateDir = checkNotNull(baseStateDir, "baseStateDir cannot be null"); mBaseStateDir.mkdirs(); if (!SELinux.restorecon(mBaseStateDir)) { Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir); } - mDataDir = dataDir; + mDataDir = checkNotNull(dataDir, "dataDir cannot be null"); mBackupPasswordManager = new BackupPasswordManager(mContext, mBaseStateDir, mRng); @@ -451,7 +516,7 @@ public class UserBackupManagerService { addPackageParticipantsLocked(null); } - mTransportManager = transportManager; + mTransportManager = checkNotNull(transportManager, "transportManager cannot be null"); mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered); mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime(); mBackupHandler.postDelayed( @@ -465,7 +530,6 @@ public class UserBackupManagerService { mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*"); } - public BackupManagerConstants getConstants() { return mConstants; } diff --git a/services/intelligence/Android.bp b/services/contentcapture/Android.bp index 2df123500a54..57e859ebe121 100644 --- a/services/intelligence/Android.bp +++ b/services/contentcapture/Android.bp @@ -1,5 +1,5 @@ java_library_static { - name: "services.intelligence", + name: "services.contentcapture", srcs: ["java/**/*.java"], libs: ["services.core"], } diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index a760cbd039e4..872fe4229479 100644 --- a/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.intelligence; +package com.android.server.contentcapture; -import static android.Manifest.permission.MANAGE_SMART_SUGGESTIONS; +import static android.Manifest.permission.MANAGE_CONTENT_CAPTURE; import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE; import android.annotation.NonNull; @@ -32,13 +32,9 @@ import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.os.UserManager; -import android.service.intelligence.InteractionSessionId; import android.util.Slog; -import android.view.autofill.AutofillId; -import android.view.autofill.AutofillValue; -import android.view.autofill.IAutoFillManagerClient; -import android.view.intelligence.ContentCaptureEvent; -import android.view.intelligence.IIntelligenceManager; +import android.view.contentcapture.ContentCaptureEvent; +import android.view.contentcapture.IContentCaptureManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; @@ -58,11 +54,10 @@ import java.util.List; * <p>The data collected by this service can be analyzed and combined with other sources to provide * contextual data in other areas of the system such as Autofill. */ -//TODO(b/111276913): rename once the final name is defined -public final class IntelligenceManagerService extends - AbstractMasterSystemService<IntelligenceManagerService, IntelligencePerUserService> { +public final class ContentCaptureManagerService extends + AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> { - private static final String TAG = "IntelligenceManagerService"; + private static final String TAG = ContentCaptureManagerService.class.getSimpleName(); static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions"; @@ -73,32 +68,32 @@ public final class IntelligenceManagerService extends private final LocalService mLocalService = new LocalService(); - public IntelligenceManagerService(Context context) { - super(context, UserManager.DISALLOW_INTELLIGENCE_CAPTURE); + public ContentCaptureManagerService(Context context) { + super(context, UserManager.DISALLOW_CONTENT_CAPTURE); } @Override // from AbstractMasterSystemService - protected IntelligencePerUserService newServiceLocked(@UserIdInt int resolvedUserId, + protected ContentCapturePerUserService newServiceLocked(@UserIdInt int resolvedUserId, boolean disabled) { - return new IntelligencePerUserService(this, mLock, resolvedUserId); + return new ContentCapturePerUserService(this, mLock, resolvedUserId); } @Override // from SystemService public void onStart() { publishBinderService(CONTENT_CAPTURE_MANAGER_SERVICE, - new IntelligenceManagerServiceStub()); - publishLocalService(IntelligenceManagerInternal.class, mLocalService); + new ContentCaptureManagerServiceStub()); + publishLocalService(ContentCaptureManagerInternal.class, mLocalService); } @Override // from AbstractMasterSystemService - protected void onServiceRemoved(@NonNull IntelligencePerUserService service, + protected void onServiceRemoved(@NonNull ContentCapturePerUserService service, @UserIdInt int userId) { service.destroyLocked(); } @Override // from AbstractMasterSystemService protected void enforceCallingPermissionForManagement() { - getContext().enforceCallingPermission(MANAGE_SMART_SUGGESTIONS, TAG); + getContext().enforceCallingPermission(MANAGE_CONTENT_CAPTURE, TAG); } @Override // from AbstractMasterSystemService @@ -113,7 +108,7 @@ public final class IntelligenceManagerService extends synchronized (mLock) { if (userId != UserHandle.USER_ALL) { - final IntelligencePerUserService service = peekServiceForUserLocked(userId); + final ContentCapturePerUserService service = peekServiceForUserLocked(userId); if (service != null) { service.destroySessionsLocked(); } @@ -139,7 +134,7 @@ public final class IntelligenceManagerService extends synchronized (mLock) { if (userId != UserHandle.USER_ALL) { - final IntelligencePerUserService service = peekServiceForUserLocked(userId); + final ContentCapturePerUserService service = peekServiceForUserLocked(userId); if (service != null) { service.listSessionsLocked(sessions); } @@ -165,11 +160,11 @@ public final class IntelligenceManagerService extends return mAm; } - final class IntelligenceManagerServiceStub extends IIntelligenceManager.Stub { + final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { @Override public void startSession(@UserIdInt int userId, @NonNull IBinder activityToken, - @NonNull ComponentName componentName, @NonNull InteractionSessionId sessionId, + @NonNull ComponentName componentName, @NonNull String sessionId, int flags, @NonNull IResultReceiver result) { Preconditions.checkNotNull(activityToken); Preconditions.checkNotNull(componentName); @@ -183,31 +178,31 @@ public final class IntelligenceManagerService extends final int displayId = 0; synchronized (mLock) { - final IntelligencePerUserService service = getServiceForUserLocked(userId); + final ContentCapturePerUserService service = getServiceForUserLocked(userId); service.startSessionLocked(activityToken, componentName, taskId, displayId, sessionId, flags, mAllowInstantService, result); } } @Override - public void sendEvents(@UserIdInt int userId, @NonNull InteractionSessionId sessionId, + public void sendEvents(@UserIdInt int userId, @NonNull String sessionId, @NonNull List<ContentCaptureEvent> events) { Preconditions.checkNotNull(sessionId); Preconditions.checkNotNull(events); synchronized (mLock) { - final IntelligencePerUserService service = getServiceForUserLocked(userId); + final ContentCapturePerUserService service = getServiceForUserLocked(userId); service.sendEventsLocked(sessionId, events); } } @Override - public void finishSession(@UserIdInt int userId, @NonNull InteractionSessionId sessionId, + public void finishSession(@UserIdInt int userId, @NonNull String sessionId, @Nullable List<ContentCaptureEvent> events) { Preconditions.checkNotNull(sessionId); synchronized (mLock) { - final IntelligencePerUserService service = getServiceForUserLocked(userId); + final ContentCapturePerUserService service = getServiceForUserLocked(userId); service.finishSessionLocked(sessionId, events); } } @@ -225,19 +220,19 @@ public final class IntelligenceManagerService extends public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) throws RemoteException { - new IntelligenceServiceShellCommand(IntelligenceManagerService.this).exec( + new ContentCaptureManagerServiceShellCommand(ContentCaptureManagerService.this).exec( this, in, out, err, args, callback, resultReceiver); } } - private final class LocalService extends IntelligenceManagerInternal { + private final class LocalService extends ContentCaptureManagerInternal { @Override - public boolean isIntelligenceServiceForUser(int uid, @UserIdInt int userId) { + public boolean isContentCaptureServiceForUser(int uid, @UserIdInt int userId) { synchronized (mLock) { - final IntelligencePerUserService service = peekServiceForUserLocked(userId); + final ContentCapturePerUserService service = peekServiceForUserLocked(userId); if (service != null) { - return service.isIntelligenceServiceForUserLocked(uid); + return service.isContentCaptureServiceForUserLocked(uid); } } return false; @@ -247,27 +242,12 @@ public final class IntelligenceManagerService extends public boolean sendActivityAssistData(@UserIdInt int userId, @NonNull IBinder activityToken, @NonNull Bundle data) { synchronized (mLock) { - final IntelligencePerUserService service = peekServiceForUserLocked(userId); + final ContentCapturePerUserService service = peekServiceForUserLocked(userId); if (service != null) { return service.sendActivityAssistDataLocked(activityToken, data); } } return false; } - - @Override - public AugmentedAutofillCallback requestAutofill(@UserIdInt int userId, - @NonNull IAutoFillManagerClient client, @NonNull IBinder activityToken, - int autofillSessionId, @NonNull AutofillId focusedId, - @Nullable AutofillValue focusedValue) { - synchronized (mLock) { - final IntelligencePerUserService service = peekServiceForUserLocked(userId); - if (service != null) { - return service.requestAutofill(client, activityToken, autofillSessionId, - focusedId, focusedValue); - } - } - return null; - } } } diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligenceServiceShellCommand.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerServiceShellCommand.java index 0d92a972aa96..2f78276bb533 100644 --- a/services/intelligence/java/com/android/server/intelligence/IntelligenceServiceShellCommand.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerServiceShellCommand.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.intelligence; +package com.android.server.contentcapture; -import static com.android.server.intelligence.IntelligenceManagerService.RECEIVER_BUNDLE_EXTRA_SESSIONS; +import static com.android.server.contentcapture.ContentCaptureManagerService.RECEIVER_BUNDLE_EXTRA_SESSIONS; import android.annotation.NonNull; import android.os.Bundle; @@ -30,14 +30,13 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** - * Shell Command implementation for {@link IntelligenceManagerService}. + * Shell Command implementation for {@link ContentCaptureManagerService}. */ -//TODO(b/111276913): rename once the final name is defined -public final class IntelligenceServiceShellCommand extends ShellCommand { +public final class ContentCaptureManagerServiceShellCommand extends ShellCommand { - private final IntelligenceManagerService mService; + private final ContentCaptureManagerService mService; - public IntelligenceServiceShellCommand(@NonNull IntelligenceManagerService service) { + public ContentCaptureManagerServiceShellCommand(@NonNull ContentCaptureManagerService service) { mService = service; } @@ -64,8 +63,7 @@ public final class IntelligenceServiceShellCommand extends ShellCommand { @Override public void onHelp() { try (PrintWriter pw = getOutPrintWriter();) { - // TODO(b/111276913): rename "intelligence" once SELinux rule changed - pw.println("Intelligence Service (intelligence) commands:"); + pw.println("ContentCapture Service (content_capture) commands:"); pw.println(" help"); pw.println(" Prints this help text."); pw.println(""); @@ -106,7 +104,7 @@ public final class IntelligenceServiceShellCommand extends ShellCommand { case "bind-instant-service-allowed": return setBindInstantService(pw); case "temporary-service": - return setTemporaryService(); + return setTemporaryService(pw); default: pw.println("Invalid set: " + what); return -1; @@ -137,7 +135,7 @@ public final class IntelligenceServiceShellCommand extends ShellCommand { } } - private int setTemporaryService() { + private int setTemporaryService(PrintWriter pw) { final int userId = getNextIntArgRequired(); final String serviceName = getNextArg(); if (serviceName == null) { @@ -146,6 +144,8 @@ public final class IntelligenceServiceShellCommand extends ShellCommand { } final int duration = getNextIntArgRequired(); mService.setTemporaryService(userId, serviceName, duration); + pw.println("ContentCaptureService temporarily set to " + serviceName + " for " + + duration + "ms"); return 0; } diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index e2b8d48ea123..aa171f4a0818 100644 --- a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.intelligence; +package com.android.server.contentcapture; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA; @@ -34,46 +34,40 @@ import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; -import android.service.intelligence.InteractionSessionId; -import android.service.intelligence.SnapshotData; +import android.service.contentcapture.SnapshotData; import android.util.ArrayMap; import android.util.Slog; -import android.view.autofill.AutofillId; -import android.view.autofill.AutofillValue; -import android.view.autofill.IAutoFillManagerClient; -import android.view.intelligence.ContentCaptureEvent; -import android.view.intelligence.ContentCaptureManager; +import android.view.contentcapture.ContentCaptureEvent; +import android.view.contentcapture.ContentCaptureManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; import com.android.server.infra.AbstractPerUserSystemService; import com.android.server.infra.FrameworkResourcesServiceNameResolver; -import com.android.server.intelligence.IntelligenceManagerInternal.AugmentedAutofillCallback; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; /** - * Per-user instance of {@link IntelligenceManagerService}. + * Per-user instance of {@link ContentCaptureManagerService}. */ -//TODO(b/111276913): rename once the final name is defined -final class IntelligencePerUserService - extends AbstractPerUserSystemService<IntelligencePerUserService, - IntelligenceManagerService> { +final class ContentCapturePerUserService + extends + AbstractPerUserSystemService<ContentCapturePerUserService, ContentCaptureManagerService> { - private static final String TAG = "IntelligencePerUserService"; + private static final String TAG = ContentCaptureManagerService.class.getSimpleName(); @GuardedBy("mLock") - private final ArrayMap<InteractionSessionId, ContentCaptureSession> mSessions = + private final ArrayMap<String, ContentCaptureSession> mSessions = new ArrayMap<>(); // TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's - protected IntelligencePerUserService( - IntelligenceManagerService master, Object lock, @UserIdInt int userId) { + protected ContentCapturePerUserService( + ContentCaptureManagerService master, Object lock, @UserIdInt int userId) { super(master, new FrameworkResourcesServiceNameResolver(master.getContext(), userId, lock, - com.android.internal.R.string.config_defaultSmartSuggestionsService), lock, userId); + com.android.internal.R.string.config_defaultContentCaptureService), lock, userId); } @Override // from PerUserSystemService @@ -98,12 +92,12 @@ final class IntelligencePerUserService + " " + serviceComponent.flattenToShortString()); return null; } - if (!Manifest.permission.BIND_SMART_SUGGESTIONS_SERVICE.equals(si.permission)) { - Slog.w(TAG, "SmartSuggestionsService from '" + si.packageName + if (!Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE.equals(si.permission)) { + Slog.w(TAG, "ContentCaptureService from '" + si.packageName + "' does not require permission " - + Manifest.permission.BIND_SMART_SUGGESTIONS_SERVICE); + + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE); throw new SecurityException("Service does not require permission " - + Manifest.permission.BIND_SMART_SUGGESTIONS_SERVICE); + + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE); } return si; } @@ -119,7 +113,7 @@ final class IntelligencePerUserService @GuardedBy("mLock") public void startSessionLocked(@NonNull IBinder activityToken, @NonNull ComponentName componentName, int taskId, int displayId, - @NonNull InteractionSessionId sessionId, int flags, boolean bindInstantServiceAllowed, + @NonNull String sessionId, int flags, boolean bindInstantServiceAllowed, @NonNull IResultReceiver resultReceiver) { if (!isEnabledLocked()) { sendToClient(resultReceiver, ContentCaptureManager.STATE_DISABLED); @@ -163,7 +157,7 @@ final class IntelligencePerUserService // TODO(b/111276913): log metrics @GuardedBy("mLock") - public void finishSessionLocked(@NonNull InteractionSessionId sessionId, + public void finishSessionLocked(@NonNull String sessionId, @Nullable List<ContentCaptureEvent> events) { if (!isEnabledLocked()) { return; @@ -180,7 +174,7 @@ final class IntelligencePerUserService // TODO(b/111276913): for now we're sending the events and the onDestroy() in 2 separate // calls because it's not clear yet whether we'll change the manager to send events // to the service directly (i.e., without passing through system server). Once we - // decide, we might need to split IIntelligenceService.onSessionLifecycle() in 2 + // decide, we might need to split IContentCaptureManager.onSessionLifecycle() in 2 // methods, one for start and another for finish (and passing the events to finish), // otherwise the service might receive the 2 calls out of order. session.sendEventsLocked(events); @@ -193,9 +187,9 @@ final class IntelligencePerUserService } // TODO(b/111276913): need to figure out why some events are sent before session is started; - // probably because IntelligenceManager is not buffering them until it gets the session back + // probably because ContentCaptureManager is not buffering them until it gets the session back @GuardedBy("mLock") - public void sendEventsLocked(@NonNull InteractionSessionId sessionId, + public void sendEventsLocked(@NonNull String sessionId, @NonNull List<ContentCaptureEvent> events) { if (!isEnabledLocked()) { return; @@ -216,7 +210,7 @@ final class IntelligencePerUserService @GuardedBy("mLock") public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken, @NonNull Bundle data) { - final InteractionSessionId id = getInteractionSessionId(activityToken); + final String id = getSessionId(activityToken); if (id != null) { final ContentCaptureSession session = mSessions.get(id); final Bundle assistData = data.getBundle(ASSIST_KEY_DATA); @@ -233,12 +227,12 @@ final class IntelligencePerUserService } @GuardedBy("mLock") - public void removeSessionLocked(@NonNull InteractionSessionId sessionId) { + public void removeSessionLocked(@NonNull String sessionId) { mSessions.remove(sessionId); } @GuardedBy("mLock") - public boolean isIntelligenceServiceForUserLocked(int uid) { + public boolean isContentCaptureServiceForUserLocked(int uid) { return uid == getServiceUidLocked(); } @@ -283,24 +277,6 @@ final class IntelligencePerUserService } } - public AugmentedAutofillCallback requestAutofill(@NonNull IAutoFillManagerClient client, - @NonNull IBinder activityToken, int autofillSessionId, @NonNull AutofillId focusedId, - @Nullable AutofillValue focusedValue) { - synchronized (mLock) { - final ContentCaptureSession session = getSession(activityToken); - if (session != null) { - // TODO(b/111330312): log metrics - if (mMaster.verbose) Slog.v(TAG, "requestAugmentedAutofill()"); - return session.requestAutofillLocked(client, autofillSessionId, focusedId, - focusedValue); - } - if (mMaster.debug) { - Slog.d(TAG, "requestAutofill(): no session for " + activityToken); - } - return null; - } - } - @Override protected void dumpLocked(String prefix, PrintWriter pw) { super.dumpLocked(prefix, pw); @@ -319,10 +295,10 @@ final class IntelligencePerUserService } /** - * Returns the InteractionSessionId associated with the given activity. + * Returns the session id associated with the given activity. */ @GuardedBy("mLock") - private InteractionSessionId getInteractionSessionId(@NonNull IBinder activityToken) { + private String getSessionId(@NonNull IBinder activityToken) { for (int i = 0; i < mSessions.size(); i++) { ContentCaptureSession session = mSessions.valueAt(i); if (session.isActivitySession(activityToken)) { diff --git a/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java index 05b8201f112e..a4012d5faa8a 100644 --- a/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java @@ -13,55 +13,48 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.intelligence; +package com.android.server.contentcapture; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.os.IBinder; -import android.service.intelligence.InteractionContext; -import android.service.intelligence.InteractionSessionId; -import android.service.intelligence.SmartSuggestionsService; -import android.service.intelligence.SnapshotData; +import android.service.contentcapture.ContentCaptureService; +import android.service.contentcapture.InteractionContext; +import android.service.contentcapture.InteractionSessionId; +import android.service.contentcapture.SnapshotData; import android.util.Slog; -import android.view.autofill.AutofillId; -import android.view.autofill.AutofillValue; -import android.view.autofill.IAutoFillManagerClient; -import android.view.intelligence.ContentCaptureEvent; +import android.view.contentcapture.ContentCaptureEvent; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; -import com.android.server.infra.AbstractRemoteService; -import com.android.server.intelligence.IntelligenceManagerInternal.AugmentedAutofillCallback; -import com.android.server.intelligence.RemoteIntelligenceService.RemoteIntelligenceServiceCallbacks; +import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks; import java.io.PrintWriter; import java.util.List; -final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks { +final class ContentCaptureSession implements ContentCaptureServiceCallbacks { private static final String TAG = "ContentCaptureSession"; private final Object mLock; final IBinder mActivityToken; - private final IntelligencePerUserService mService; - private final RemoteIntelligenceService mRemoteService; + private final ContentCapturePerUserService mService; + private final RemoteContentCaptureService mRemoteService; private final InteractionContext mInterationContext; - private final InteractionSessionId mId; - private AugmentedAutofillCallback mAutofillCallback; + private final String mId; ContentCaptureSession(@NonNull Context context, int userId, @NonNull Object lock, - @NonNull IBinder activityToken, @NonNull IntelligencePerUserService service, + @NonNull IBinder activityToken, @NonNull ContentCapturePerUserService service, @NonNull ComponentName serviceComponentName, @NonNull ComponentName appComponentName, - int taskId, int displayId, @NonNull InteractionSessionId sessionId, int flags, + int taskId, int displayId, @NonNull String sessionId, int flags, boolean bindInstantServiceAllowed, boolean verbose) { mLock = lock; mActivityToken = activityToken; mService = service; mId = Preconditions.checkNotNull(sessionId); - mRemoteService = new RemoteIntelligenceService(context, - SmartSuggestionsService.SERVICE_INTERFACE, serviceComponentName, userId, this, + mRemoteService = new RemoteContentCaptureService(context, + ContentCaptureService.SERVICE_INTERFACE, serviceComponentName, userId, this, bindInstantServiceAllowed, verbose); mInterationContext = new InteractionContext(appComponentName, taskId, displayId, flags); } @@ -74,7 +67,7 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks } /** - * Notifies the {@link SmartSuggestionsService} that the service started. + * Notifies the {@link ContentCaptureService} that the service started. */ @GuardedBy("mLock") public void notifySessionStartedLocked() { @@ -82,14 +75,14 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks } /** - * Notifies the {@link SmartSuggestionsService} of a batch of events. + * Notifies the {@link ContentCaptureService} of a batch of events. */ public void sendEventsLocked(@NonNull List<ContentCaptureEvent> events) { mRemoteService.onContentCaptureEventsRequest(mId, events); } /** - * Notifies the {@link SmartSuggestionsService} of a snapshot of an activity. + * Notifies the {@link ContentCaptureService} of a snapshot of an activity. */ @GuardedBy("mLock") public void sendActivitySnapshotLocked(@NonNull SnapshotData snapshotData) { @@ -97,24 +90,10 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks } /** - * Requests the service to autofill the given field. - */ - public AugmentedAutofillCallback requestAutofillLocked(@NonNull IAutoFillManagerClient client, - int autofillSessionId, @NonNull AutofillId focusedId, - @Nullable AutofillValue focusedValue) { - mRemoteService.onRequestAutofillLocked(mId, client, autofillSessionId, focusedId, - focusedValue); - if (mAutofillCallback == null) { - mAutofillCallback = () -> mRemoteService.onDestroyAutofillWindowsRequest(mId); - } - return mAutofillCallback; - } - - /** * Cleans up the session and removes it from the service. * * @param notifyRemoteService whether it should trigger a {@link - * SmartSuggestionsService#onDestroyInteractionSession(InteractionSessionId)} + * ContentCaptureService#onDestroyInteractionSession(InteractionSessionId)} * request. */ @GuardedBy("mLock") @@ -130,7 +109,7 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks * Cleans up the session, but not removes it from the service. * * @param notifyRemoteService whether it should trigger a {@link - * SmartSuggestionsService#onDestroyInteractionSession(InteractionSessionId)} + * ContentCaptureService#onDestroyInteractionSession(InteractionSessionId)} * request. */ @GuardedBy("mLock") @@ -138,19 +117,14 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks if (mService.isVerbose()) { Slog.v(TAG, "destroyLocked(notifyRemoteService=" + notifyRemoteService + ")"); } - if (mAutofillCallback != null) { - mAutofillCallback.destroy(); - mAutofillCallback = null; - } - // TODO(b/111276913): must call client to set session as FINISHED_BY_SERVER if (notifyRemoteService) { mRemoteService.onSessionLifecycleRequest(/* context= */ null, mId); } } - @Override // from RemoteScreenObservationServiceCallbacks - public void onServiceDied(AbstractRemoteService<?> service) { + @Override // from RemoteContentCaptureServiceCallbacks + public void onServiceDied(@NonNull RemoteContentCaptureService service) { // TODO(b/111276913): implement (remove session from PerUserSession?) if (mService.isDebug()) { Slog.d(TAG, "onServiceDied() for " + mId); @@ -160,32 +134,20 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks } } - @Override // from RemoteScreenObservationServiceCallbacks - public void onFailureOrTimeout(boolean timedOut) { - // TODO(b/111276913): log metrics on whether timed out or not - if (mService.isDebug()) { - Slog.d(TAG, "onFailureOrTimeout(" + mId + "): timed out=" + timedOut); - } - synchronized (mLock) { - removeSelfLocked(/* notifyRemoteService= */ false); - } - } - @GuardedBy("mLock") public void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) { - pw.print(prefix); pw.print("id: "); mId.dump(pw); pw.println(); + pw.print(prefix); pw.print("id: "); pw.print(mId); pw.println(); pw.print(prefix); pw.print("context: "); mInterationContext.dump(pw); pw.println(); pw.print(prefix); pw.print("activity token: "); pw.println(mActivityToken); pw.print(prefix); pw.print("has autofill callback: "); - pw.println(mAutofillCallback != null); } String toShortString() { - return mId.getValue() + ":" + mActivityToken; + return mId + ":" + mActivityToken; } @Override public String toString() { - return "ContentCaptureSession[id=" + mId.getValue() + ", act=" + mActivityToken + "]"; + return "ContentCaptureSession[id=" + mId + ", act=" + mActivityToken + "]"; } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java new file mode 100644 index 000000000000..33b6c8d5eec4 --- /dev/null +++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2018 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.contentcapture; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.os.IBinder; +import android.service.contentcapture.ContentCaptureEventsRequest; +import android.service.contentcapture.IContentCaptureService; +import android.service.contentcapture.InteractionContext; +import android.service.contentcapture.SnapshotData; +import android.text.format.DateUtils; +import android.view.contentcapture.ContentCaptureEvent; + +import com.android.server.infra.AbstractMultiplePendingRequestsRemoteService; + +import java.util.List; + +final class RemoteContentCaptureService + extends AbstractMultiplePendingRequestsRemoteService<RemoteContentCaptureService, + IContentCaptureService> { + + // TODO(b/117779333): changed it so it's permanentely bound + private static final long TIMEOUT_IDLE_BIND_MILLIS = 2 * DateUtils.MINUTE_IN_MILLIS; + private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS; + + RemoteContentCaptureService(Context context, String serviceInterface, + ComponentName componentName, int userId, + ContentCaptureServiceCallbacks callbacks, boolean bindInstantServiceAllowed, + boolean verbose) { + super(context, serviceInterface, componentName, userId, callbacks, + bindInstantServiceAllowed, verbose, /* initialCapacity= */ 2); + } + + @Override // from RemoteService + protected IContentCaptureService getServiceInterface(@NonNull IBinder service) { + return IContentCaptureService.Stub.asInterface(service); + } + + // TODO(b/111276913): modify super class to allow permanent binding when value is 0 or negative + @Override // from RemoteService + protected long getTimeoutIdleBindMillis() { + // TODO(b/111276913): read from Settings so it can be changed in the field + return TIMEOUT_IDLE_BIND_MILLIS; + } + + @Override // from RemoteService + protected long getRemoteRequestMillis() { + // TODO(b/111276913): read from Settings so it can be changed in the field + return TIMEOUT_REMOTE_REQUEST_MILLIS; + } + + /** + * Called by {@link ContentCaptureSession} to generate a call to the + * {@link RemoteContentCaptureService} to indicate the session was created (when {@code context} + * is not {@code null} or destroyed (when {@code context} is {@code null}). + */ + public void onSessionLifecycleRequest(@Nullable InteractionContext context, + @NonNull String sessionId) { + scheduleAsyncRequest((s) -> s.onSessionLifecycle(context, sessionId)); + } + + /** + * Called by {@link ContentCaptureSession} to send a batch of events to the service. + */ + public void onContentCaptureEventsRequest(@NonNull String sessionId, + @NonNull List<ContentCaptureEvent> events) { + scheduleAsyncRequest((s) -> s.onContentCaptureEventsRequest(sessionId, + new ContentCaptureEventsRequest(events))); + } + + /** + * Called by {@link ContentCaptureSession} to send snapshot data to the service. + */ + public void onActivitySnapshotRequest(@NonNull String sessionId, + @NonNull SnapshotData snapshotData) { + scheduleAsyncRequest((s) -> s.onActivitySnapshot(sessionId, snapshotData)); + } + + public interface ContentCaptureServiceCallbacks + extends VultureCallback<RemoteContentCaptureService> { + // NOTE: so far we don't need to notify the callback implementation (an inner class on + // AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this + // callback interface is empty. + } +} diff --git a/services/core/Android.bp b/services/core/Android.bp index 784d398a2b3f..4408db895537 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -7,7 +7,6 @@ java_library_static { "frameworks/native/aidl/binder", "frameworks/native/cmds/dumpstate/binder", "system/core/storaged/binder", - "system/netd/server/binder", "system/vold/binder", ], }, @@ -15,8 +14,6 @@ java_library_static { "java/**/*.java", ":dumpstate_aidl", ":idmap2_aidl", - ":netd_aidl", - ":netd_metrics_aidl", ":installd_aidl", ":storaged_aidl", ":vold_aidl", @@ -53,6 +50,7 @@ java_library_static { "android.hardware.contexthub-V1.0-java", "android.hidl.manager-V1.0-java", "netd_aidl_interface-java", + "netd_event_listener_interface-java", ], } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 854c03f128dd..08034f734bea 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -48,6 +48,7 @@ import android.content.pm.PermissionInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; @@ -1306,8 +1307,12 @@ class AlarmManagerService extends SystemService { // because kernel doesn't keep this after reboot setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY)); - // Also sure that we're booting with a halfway sensible current time - final long systemBuildTime = Environment.getRootDirectory().lastModified(); + // Ensure that we're booting with a halfway sensible current time. Use the + // most recent of Build.TIME, the root file system's timestamp, and the + // value of the ro.build.date.utc system property (which is in seconds). + final long systemBuildTime = Long.max( + 1000L * SystemProperties.getLong("ro.build.date.utc", -1L), + Long.max(Environment.getRootDirectory().lastModified(), Build.TIME)); if (mInjector.getCurrentTimeMillis() < systemBuildTime) { Slog.i(TAG, "Current time only " + mInjector.getCurrentTimeMillis() + ", advancing to build time " + systemBuildTime); diff --git a/services/core/java/com/android/server/AnyMotionDetector.java b/services/core/java/com/android/server/AnyMotionDetector.java index d56492521276..8c5ee7f35027 100644 --- a/services/core/java/com/android/server/AnyMotionDetector.java +++ b/services/core/java/com/android/server/AnyMotionDetector.java @@ -26,8 +26,6 @@ import android.os.PowerManager; import android.os.SystemClock; import android.util.Slog; -import java.lang.Float; - /** * Determines if the device has been set upon a stationary object. */ @@ -140,6 +138,13 @@ public class AnyMotionDetector { } } + /** + * If we do not have an accelerometer, we are not going to collect much data. + */ + public boolean hasSensor() { + return mAccelSensor != null; + } + /* * Acquire accel data until we determine AnyMotion status. */ diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java index 11a2fc9c1e45..8b7f321eb087 100644 --- a/services/core/java/com/android/server/BinderCallsStatsService.java +++ b/services/core/java/com/android/server/BinderCallsStatsService.java @@ -19,7 +19,6 @@ package com.android.server; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; -import android.app.AppGlobals; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -28,9 +27,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; import android.os.Process; -import android.os.RemoteException; import android.os.SystemProperties; -import android.os.ThreadLocalWorkSource; import android.os.UserHandle; import android.provider.Settings; import android.util.ArrayMap; @@ -38,19 +35,16 @@ import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Slog; -import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.AppIdToPackageMap; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.BinderInternal; -import com.android.internal.os.BinderInternal.CallSession; import com.android.internal.os.CachedDeviceState; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; public class BinderCallsStatsService extends Binder { @@ -60,10 +54,10 @@ public class BinderCallsStatsService extends Binder { = "persist.sys.binder_calls_detailed_tracking"; /** Resolves the work source of an incoming binder transaction. */ - static class WorkSourceProvider { + static class AuthorizedWorkSourceProvider implements BinderInternal.WorkSourceProvider { private ArraySet<Integer> mAppIdWhitelist; - WorkSourceProvider() { + AuthorizedWorkSourceProvider() { mAppIdWhitelist = new ArraySet<>(); } @@ -82,11 +76,11 @@ public class BinderCallsStatsService extends Binder { mAppIdWhitelist = createAppidWhitelist(context); } - public void dump(PrintWriter pw, Map<Integer, String> appIdToPackageName) { + public void dump(PrintWriter pw, AppIdToPackageMap packageMap) { pw.println("AppIds of apps that can set the work source:"); final ArraySet<Integer> whitelist = mAppIdWhitelist; for (Integer appId : whitelist) { - pw.println("\t- " + appIdToPackageName.getOrDefault(appId, String.valueOf(appId))); + pw.println("\t- " + packageMap.mapAppId(appId)); } } @@ -103,7 +97,7 @@ public class BinderCallsStatsService extends Binder { final ArraySet<Integer> whitelist = new ArraySet<>(); // We trust our own process. - whitelist.add(Process.myUid()); + whitelist.add(UserHandle.getAppId(Process.myUid())); // We only need to initialize it once. UPDATE_DEVICE_STATS is a system permission. final PackageManager pm = context.getPackageManager(); final String[] permissions = { android.Manifest.permission.UPDATE_DEVICE_STATS }; @@ -125,41 +119,6 @@ public class BinderCallsStatsService extends Binder { } } - /** Observer for all system server incoming binder transactions. */ - @VisibleForTesting - static class BinderCallsObserver implements BinderInternal.Observer { - private final BinderInternal.Observer mBinderCallsStats; - private final WorkSourceProvider mWorkSourceProvider; - - BinderCallsObserver(BinderInternal.Observer callsStats, - WorkSourceProvider workSourceProvider) { - mBinderCallsStats = callsStats; - mWorkSourceProvider = workSourceProvider; - } - - @Override - public CallSession callStarted(Binder binder, int code) { - // We depend on the code in Binder#execTransact to reset the state of - // ThreadLocalWorkSource - setThreadLocalWorkSourceUid(mWorkSourceProvider.resolveWorkSourceUid()); - return mBinderCallsStats.callStarted(binder, code); - } - - @Override - public void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize) { - mBinderCallsStats.callEnded(s, parcelRequestSize, parcelReplySize); - } - - @Override - public void callThrewException(CallSession s, Exception exception) { - mBinderCallsStats.callThrewException(s, exception); - } - - protected void setThreadLocalWorkSourceUid(int uid) { - ThreadLocalWorkSource.setUid(uid); - } - } - /** Listens for flag changes. */ private static class SettingsObserver extends ContentObserver { private static final String SETTINGS_ENABLED_KEY = "enabled"; @@ -173,16 +132,16 @@ public class BinderCallsStatsService extends Binder { private final Context mContext; private final KeyValueListParser mParser = new KeyValueListParser(','); private final BinderCallsStats mBinderCallsStats; - private final BinderCallsObserver mBinderCallsObserver; + private final AuthorizedWorkSourceProvider mWorkSourceProvider; SettingsObserver(Context context, BinderCallsStats binderCallsStats, - BinderCallsObserver observer) { + AuthorizedWorkSourceProvider workSourceProvider) { super(BackgroundThread.getHandler()); mContext = context; context.getContentResolver().registerContentObserver(mUri, false, this, UserHandle.USER_SYSTEM); mBinderCallsStats = binderCallsStats; - mBinderCallsObserver = observer; + mWorkSourceProvider = workSourceProvider; // Always kick once to ensure that we match current state onChange(); } @@ -220,12 +179,14 @@ public class BinderCallsStatsService extends Binder { mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT); if (mEnabled != enabled) { if (enabled) { - Binder.setObserver(mBinderCallsObserver); + Binder.setObserver(mBinderCallsStats); Binder.setProxyTransactListener( new Binder.PropagateWorkSourceTransactListener()); + Binder.setWorkSourceProvider(mWorkSourceProvider); } else { Binder.setObserver(null); Binder.setProxyTransactListener(null); + Binder.setWorkSourceProvider(Binder::getCallingUid); } mEnabled = enabled; mBinderCallsStats.reset(); @@ -268,7 +229,7 @@ public class BinderCallsStatsService extends Binder { public static class LifeCycle extends SystemService { private BinderCallsStatsService mService; private BinderCallsStats mBinderCallsStats; - private WorkSourceProvider mWorkSourceProvider; + private AuthorizedWorkSourceProvider mWorkSourceProvider; public LifeCycle(Context context) { super(context); @@ -277,11 +238,9 @@ public class BinderCallsStatsService extends Binder { @Override public void onStart() { mBinderCallsStats = new BinderCallsStats(new BinderCallsStats.Injector()); - mWorkSourceProvider = new WorkSourceProvider(); - BinderCallsObserver binderCallsObserver = - new BinderCallsObserver(mBinderCallsStats, mWorkSourceProvider); + mWorkSourceProvider = new AuthorizedWorkSourceProvider(); mService = new BinderCallsStatsService( - mBinderCallsStats, binderCallsObserver, mWorkSourceProvider); + mBinderCallsStats, mWorkSourceProvider); publishLocalService(Internal.class, new Internal(mBinderCallsStats)); publishBinderService("binder_calls_stats", mService); boolean detailedTrackingEnabled = SystemProperties.getBoolean( @@ -311,18 +270,16 @@ public class BinderCallsStatsService extends Binder { private SettingsObserver mSettingsObserver; private final BinderCallsStats mBinderCallsStats; - private final BinderCallsObserver mBinderCallsObserver; - private final WorkSourceProvider mWorkSourceProvider; + private final AuthorizedWorkSourceProvider mWorkSourceProvider; - BinderCallsStatsService(BinderCallsStats binderCallsStats, BinderCallsObserver observer, - WorkSourceProvider workSourceProvider) { + BinderCallsStatsService(BinderCallsStats binderCallsStats, + AuthorizedWorkSourceProvider workSourceProvider) { mBinderCallsStats = binderCallsStats; - mBinderCallsObserver = observer; mWorkSourceProvider = workSourceProvider; } public void systemReady(Context context) { - mSettingsObserver = new SettingsObserver(context, mBinderCallsStats, mBinderCallsObserver); + mSettingsObserver = new SettingsObserver(context, mBinderCallsStats, mWorkSourceProvider); } public void reset() { @@ -342,7 +299,7 @@ public class BinderCallsStatsService extends Binder { pw.println("binder_calls_stats reset."); return; } else if ("--enable".equals(arg)) { - Binder.setObserver(mBinderCallsObserver); + Binder.setObserver(mBinderCallsStats); return; } else if ("--disable".equals(arg)) { Binder.setObserver(null); @@ -361,7 +318,7 @@ public class BinderCallsStatsService extends Binder { pw.println("Detailed tracking disabled"); return; } else if ("--dump-worksource-provider".equals(arg)) { - mWorkSourceProvider.dump(pw, getAppIdToPackagesMap()); + mWorkSourceProvider.dump(pw, AppIdToPackageMap.getSnapshot()); return; } else if ("-h".equals(arg)) { pw.println("binder_calls_stats commands:"); @@ -377,28 +334,6 @@ public class BinderCallsStatsService extends Binder { } } } - mBinderCallsStats.dump(pw, getAppIdToPackagesMap(), verbose); - } - - private Map<Integer, String> getAppIdToPackagesMap() { - List<PackageInfo> packages; - try { - packages = AppGlobals.getPackageManager() - .getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES, - UserHandle.USER_SYSTEM).getList(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - Map<Integer,String> map = new HashMap<>(); - for (PackageInfo pkg : packages) { - String name = pkg.packageName; - int uid = pkg.applicationInfo.uid; - // Use sharedUserId string as package name if there are collisions - if (pkg.sharedUserId != null && map.containsKey(uid)) { - name = "shared:" + pkg.sharedUserId; - } - map.put(uid, name); - } - return map; + mBinderCallsStats.dump(pw, AppIdToPackageMap.getSnapshot(), verbose); } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 14503f9d7379..eda9fe15fe36 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -902,6 +902,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Listen to package add and removal events for all users. intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); mContext.registerReceiverAsUser( @@ -4203,12 +4204,46 @@ public class ConnectivityService extends IConnectivityManager.Stub mPermissionMonitor.onPackageAdded(packageName, uid); } - private void onPackageRemoved(String packageName, int uid) { + private void onPackageReplaced(String packageName, int uid) { + if (TextUtils.isEmpty(packageName) || uid < 0) { + Slog.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid); + return; + } + final int userId = UserHandle.getUserId(uid); + synchronized (mVpns) { + final Vpn vpn = mVpns.get(userId); + if (vpn == null) { + return; + } + // Legacy always-on VPN won't be affected since the package name is not set. + if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { + Slog.d(TAG, "Restarting always-on VPN package " + packageName + " for user " + + userId); + vpn.startAlwaysOnVpn(); + } + } + } + + private void onPackageRemoved(String packageName, int uid, boolean isReplacing) { if (TextUtils.isEmpty(packageName) || uid < 0) { Slog.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid); return; } mPermissionMonitor.onPackageRemoved(uid); + + final int userId = UserHandle.getUserId(uid); + synchronized (mVpns) { + final Vpn vpn = mVpns.get(userId); + if (vpn == null) { + return; + } + // Legacy always-on VPN won't be affected since the package name is not set. + if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { + Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user " + + userId); + vpn.setAlwaysOnPackage(null, false); + } + } } private void onUserUnlocked(int userId) { @@ -4245,8 +4280,12 @@ public class ConnectivityService extends IConnectivityManager.Stub onUserUnlocked(userId); } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { onPackageAdded(packageName, uid); + } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { + onPackageReplaced(packageName, uid); } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { - onPackageRemoved(packageName, uid); + final boolean isReplacing = intent.getBooleanExtra( + Intent.EXTRA_REPLACING, false); + onPackageRemoved(packageName, uid, isReplacing); } } }; diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index af9d4c8c69b6..08cb7a2f5047 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -43,6 +43,7 @@ import android.net.ConnectivityManager; import android.net.INetworkPolicyManager; import android.net.NetworkInfo; import android.net.Uri; +import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Binder; import android.os.Bundle; @@ -272,6 +273,7 @@ public class DeviceIdleController extends SystemService private PowerManager mPowerManager; private INetworkPolicyManager mNetworkPolicyManager; private SensorManager mSensorManager; + private final boolean mUseMotionSensor; private Sensor mMotionSensor; private LocationRequest mLocationRequest; private Intent mIdleIntent; @@ -520,9 +522,10 @@ public class DeviceIdleController extends SystemService updateConnectivityState(intent); } break; case Intent.ACTION_BATTERY_CHANGED: { + boolean present = intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true); + boolean plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; synchronized (DeviceIdleController.this) { - int plugged = intent.getIntExtra("plugged", 0); - updateChargingLocked(plugged != 0); + updateChargingLocked(present && plugged); } } break; case Intent.ACTION_PACKAGE_REMOVED: { @@ -1629,6 +1632,9 @@ public class DeviceIdleController extends SystemService mHandler = mInjector.getHandler(this); mAppStateTracker = mInjector.getAppStateTracker(context, FgThread.get().getLooper()); LocalServices.addService(AppStateTracker.class, mAppStateTracker); + + mUseMotionSensor = context.getResources().getBoolean( + com.android.internal.R.bool.config_autoPowerModeUseMotionSensor); } public DeviceIdleController(Context context) { @@ -1729,20 +1735,23 @@ public class DeviceIdleController extends SystemService ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class); mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE); - int sigMotionSensorId = getContext().getResources().getInteger( - com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor); - if (sigMotionSensorId > 0) { - mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true); - } - if (mMotionSensor == null && getContext().getResources().getBoolean( - com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) { - mMotionSensor = mSensorManager.getDefaultSensor( - Sensor.TYPE_WRIST_TILT_GESTURE, true); - } - if (mMotionSensor == null) { - // As a last ditch, fall back to SMD. - mMotionSensor = mSensorManager.getDefaultSensor( - Sensor.TYPE_SIGNIFICANT_MOTION, true); + + if (mUseMotionSensor) { + int sigMotionSensorId = getContext().getResources().getInteger( + com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor); + if (sigMotionSensorId > 0) { + mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true); + } + if (mMotionSensor == null && getContext().getResources().getBoolean( + com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) { + mMotionSensor = mSensorManager.getDefaultSensor( + Sensor.TYPE_WRIST_TILT_GESTURE, true); + } + if (mMotionSensor == null) { + // As a last ditch, fall back to SMD. + mMotionSensor = mSensorManager.getDefaultSensor( + Sensor.TYPE_SIGNIFICANT_MOTION, true); + } } if (getContext().getResources().getBoolean( @@ -2588,14 +2597,21 @@ public class DeviceIdleController extends SystemService mState = STATE_SENSING; if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING."); EventLogTags.writeDeviceIdle(mState, reason); - scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT); cancelLocatingLocked(); - mNotMoving = false; mLocated = false; mLastGenericLocation = null; mLastGpsLocation = null; - mAnyMotionDetector.checkForAnyMotion(); - break; + + // If we have an accelerometer, wait to find out whether we are moving. + if (mUseMotionSensor && mAnyMotionDetector.hasSensor()) { + scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT); + mNotMoving = false; + mAnyMotionDetector.checkForAnyMotion(); + break; + } + + mNotMoving = true; + // Otherwise, fall through and check this off the list of requirements. case STATE_SENSING: cancelSensingTimeoutAlarmLocked(); mState = STATE_LOCATING; @@ -2893,9 +2909,12 @@ public class DeviceIdleController extends SystemService void scheduleAlarmLocked(long delay, boolean idleUntil) { if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + idleUntil + ")"); - if (mMotionSensor == null && !(mState == STATE_QUICK_DOZE_DELAY || mState == STATE_IDLE - || mState == STATE_IDLE_MAINTENANCE)) { - // If there is no motion sensor on this device, then we won't schedule + + if (mUseMotionSensor && mMotionSensor == null + && mState != STATE_QUICK_DOZE_DELAY + && mState != STATE_IDLE + && mState != STATE_IDLE_MAINTENANCE) { + // If there is no motion sensor on this device, but we need one, then we won't schedule // alarms, because we can't determine if the device is not moving. This effectively // turns off normal execution of device idling, although it is still possible to // manually poke it by pretending like the alarm is going off. @@ -3765,13 +3784,20 @@ public class DeviceIdleController extends SystemService pw.print(" mLightEnabled="); pw.print(mLightEnabled); pw.print(" mDeepEnabled="); pw.println(mDeepEnabled); pw.print(" mForceIdle="); pw.println(mForceIdle); - pw.print(" mMotionSensor="); pw.println(mMotionSensor); + pw.print(" mUseMotionSensor="); pw.print(mUseMotionSensor); + if (mUseMotionSensor) { + pw.print(" mMotionSensor="); pw.println(mMotionSensor); + } else { + pw.println(); + } pw.print(" mScreenOn="); pw.println(mScreenOn); pw.print(" mScreenLocked="); pw.println(mScreenLocked); pw.print(" mNetworkConnected="); pw.println(mNetworkConnected); pw.print(" mCharging="); pw.println(mCharging); pw.print(" mMotionActive="); pw.println(mMotionListener.active); - pw.print(" mNotMoving="); pw.println(mNotMoving); + if (mUseMotionSensor) { + pw.print(" mNotMoving="); pw.println(mNotMoving); + } pw.print(" mLocating="); pw.print(mLocating); pw.print(" mHasGps="); pw.print(mHasGps); pw.print(" mHasNetwork="); pw.print(mHasNetworkLocation); pw.print(" mLocated="); pw.println(mLocated); diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index cc7bf3373bdd..8b992ebcf059 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -17,8 +17,11 @@ package com.android.server; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.location.LocationProvider.AVAILABLE; import static android.provider.Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS; +import static com.android.internal.util.Preconditions.checkState; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -47,14 +50,12 @@ import android.location.IBatchedLocationCallback; import android.location.IGnssMeasurementsListener; import android.location.IGnssNavigationMessageListener; import android.location.IGnssStatusListener; -import android.location.IGnssStatusProvider; import android.location.IGpsGeofenceHardware; import android.location.ILocationListener; import android.location.ILocationManager; import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationManager; -import android.location.LocationProvider; import android.location.LocationRequest; import android.os.Binder; import android.os.Bundle; @@ -84,6 +85,7 @@ import com.android.internal.location.ProviderRequest; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; +import com.android.server.location.AbstractLocationProvider; import com.android.server.location.ActivityRecognitionProxy; import com.android.server.location.GeocoderProxy; import com.android.server.location.GeofenceManager; @@ -92,9 +94,9 @@ import com.android.server.location.GnssBatchingProvider; import com.android.server.location.GnssLocationProvider; import com.android.server.location.GnssMeasurementsProvider; import com.android.server.location.GnssNavigationMessageProvider; +import com.android.server.location.GnssStatusListenerHelper; import com.android.server.location.LocationBlacklist; import com.android.server.location.LocationFudger; -import com.android.server.location.LocationProviderInterface; import com.android.server.location.LocationProviderProxy; import com.android.server.location.LocationRequestStatistics; import com.android.server.location.LocationRequestStatistics.PackageProviderKey; @@ -131,8 +133,6 @@ public class LocationManagerService extends ILocationManager.Stub { // Location resolution level: fine location data private static final int RESOLUTION_LEVEL_FINE = 2; - private static final String ACCESS_MOCK_LOCATION = - android.Manifest.permission.ACCESS_MOCK_LOCATION; private static final String ACCESS_LOCATION_EXTRA_COMMANDS = android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS; private static final String INSTALL_LOCATION_PROVIDER = @@ -182,7 +182,7 @@ public class LocationManagerService extends ILocationManager.Stub { private ActivityManager mActivityManager; private UserManager mUserManager; private GeocoderProxy mGeocodeProvider; - private IGnssStatusProvider mGnssStatusProvider; + private GnssStatusListenerHelper mGnssStatusProvider; private INetInitiatedListener mNetInitiatedListener; private LocationWorkerHandler mLocationHandler; private PassiveProvider mPassiveProvider; // track passive provider for special cases @@ -192,12 +192,6 @@ public class LocationManagerService extends ILocationManager.Stub { private IGpsGeofenceHardware mGpsGeofenceProxy; // --- fields below are protected by mLock --- - // Set of providers that are explicitly enabled - // Only used by passive, fused & test. Network & GPS are controlled separately, and not listed. - private final Set<String> mEnabledProviders = new HashSet<>(); - - // Set of providers that are explicitly disabled - private final Set<String> mDisabledProviders = new HashSet<>(); // Mock (test) providers private final HashMap<String, MockProvider> mMockProviders = @@ -207,15 +201,15 @@ public class LocationManagerService extends ILocationManager.Stub { private final HashMap<Object, Receiver> mReceivers = new HashMap<>(); // currently installed providers (with mocks replacing real providers) - private final ArrayList<LocationProviderInterface> mProviders = + private final ArrayList<LocationProvider> mProviders = new ArrayList<>(); // real providers, saved here when mocked out - private final HashMap<String, LocationProviderInterface> mRealProviders = + private final HashMap<String, LocationProvider> mRealProviders = new HashMap<>(); // mapping from provider name to provider - private final HashMap<String, LocationProviderInterface> mProvidersByName = + private final HashMap<String, LocationProvider> mProvidersByName = new HashMap<>(); // mapping from provider name to all its UpdateRecords @@ -270,13 +264,8 @@ public class LocationManagerService extends ILocationManager.Stub { PackageManagerInternal packageManagerInternal = LocalServices.getService( PackageManagerInternal.class); packageManagerInternal.setLocationPackagesProvider( - new PackageManagerInternal.PackagesProvider() { - @Override - public String[] getPackages(int userId) { - return mContext.getResources().getStringArray( - com.android.internal.R.array.config_locationProviderPackageNames); - } - }); + userId -> mContext.getResources().getStringArray( + com.android.internal.R.array.config_locationProviderPackageNames)); if (D) Log.d(TAG, "Constructed"); @@ -321,30 +310,17 @@ public class LocationManagerService extends ILocationManager.Stub { mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null, AppOpsManager.WATCH_FOREGROUND_CHANGES, callback); - PackageManager.OnPermissionsChangedListener permissionListener - = new PackageManager.OnPermissionsChangedListener() { - @Override - public void onPermissionsChanged(final int uid) { - synchronized (mLock) { - applyAllProviderRequirementsLocked(); - } + PackageManager.OnPermissionsChangedListener permissionListener = uid -> { + synchronized (mLock) { + applyAllProviderRequirementsLocked(); } }; mPackageManager.addOnPermissionsChangeListener(permissionListener); // listen for background/foreground changes - ActivityManager.OnUidImportanceListener uidImportanceListener - = new ActivityManager.OnUidImportanceListener() { - @Override - public void onUidImportance(final int uid, final int importance) { - mLocationHandler.post(new Runnable() { - @Override - public void run() { - onUidImportanceChanged(uid, importance); - } - }); - } - }; + ActivityManager.OnUidImportanceListener uidImportanceListener = + (uid, importance) -> mLocationHandler.post( + () -> onUidImportanceChanged(uid, importance)); mActivityManager.addOnUidImportanceListener(uidImportanceListener, FOREGROUND_IMPORTANCE_CUTOFF); @@ -356,7 +332,10 @@ public class LocationManagerService extends ILocationManager.Stub { // prepare providers loadProvidersLocked(); - updateProvidersLocked(); + updateProvidersSettingsLocked(); + for (LocationProvider provider : mProviders) { + applyRequirementsLocked(provider.getName()); + } } // listen for settings changes @@ -366,7 +345,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void onChange(boolean selfChange) { synchronized (mLock) { - updateProvidersLocked(); + updateProvidersSettingsLocked(); } } }, UserHandle.USER_ALL); @@ -377,7 +356,9 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void onChange(boolean selfChange) { synchronized (mLock) { - updateProvidersLocked(); + for (LocationProvider provider : mProviders) { + applyRequirementsLocked(provider.getName()); + } } } }, UserHandle.USER_ALL); @@ -402,7 +383,9 @@ public class LocationManagerService extends ILocationManager.Stub { public void onChange(boolean selfChange) { synchronized (mLock) { updateBackgroundThrottlingWhitelistLocked(); - updateProvidersLocked(); + for (LocationProvider provider : mProviders) { + applyRequirementsLocked(provider.getName()); + } } } }, UserHandle.USER_ALL); @@ -414,7 +397,6 @@ public class LocationManagerService extends ILocationManager.Stub { intentFilter.addAction(Intent.ACTION_USER_SWITCHED); intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); - intentFilter.addAction(Intent.ACTION_SHUTDOWN); mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override @@ -425,12 +407,6 @@ public class LocationManagerService extends ILocationManager.Stub { } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action) || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { updateUserProfiles(mCurrentUserId); - } else if (Intent.ACTION_SHUTDOWN.equals(action)) { - // shutdown only if UserId indicates whole system, not just one user - if (D) Log.d(TAG, "Shutdown received with UserId: " + getSendingUserId()); - if (getSendingUserId() == UserHandle.USER_ALL) { - shutdownComponents(); - } } } }, UserHandle.ALL, intentFilter, null, mLocationHandler); @@ -463,14 +439,16 @@ public class LocationManagerService extends ILocationManager.Stub { } for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) { - if (entry.getValue().mUid == uid) { + Identity callerIdentity = entry.getValue(); + if (callerIdentity.mUid == uid) { if (D) { Log.d(TAG, "gnss measurements listener from uid " + uid + " is now " + (foreground ? "foreground" : "background)")); } if (foreground || isThrottlingExemptLocked(entry.getValue())) { mGnssMeasurementsProvider.addListener( - IGnssMeasurementsListener.Stub.asInterface(entry.getKey())); + IGnssMeasurementsListener.Stub.asInterface(entry.getKey()), + callerIdentity.mUid, callerIdentity.mPackageName); } else { mGnssMeasurementsProvider.removeListener( IGnssMeasurementsListener.Stub.asInterface(entry.getKey())); @@ -479,7 +457,8 @@ public class LocationManagerService extends ILocationManager.Stub { } for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) { - if (entry.getValue().mUid == uid) { + Identity callerIdentity = entry.getValue(); + if (callerIdentity.mUid == uid) { if (D) { Log.d(TAG, "gnss navigation message listener from uid " + uid + " is now " @@ -487,13 +466,16 @@ public class LocationManagerService extends ILocationManager.Stub { } if (foreground || isThrottlingExemptLocked(entry.getValue())) { mGnssNavigationMessageProvider.addListener( - IGnssNavigationMessageListener.Stub.asInterface(entry.getKey())); + IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()), + callerIdentity.mUid, callerIdentity.mPackageName); } else { mGnssNavigationMessageProvider.removeListener( IGnssNavigationMessageListener.Stub.asInterface(entry.getKey())); } } } + + // TODO(b/120449926): The GNSS status listeners should be handled similar to the above. } } @@ -502,30 +484,13 @@ public class LocationManagerService extends ILocationManager.Stub { } /** - * Provides a way for components held by the {@link LocationManagerService} to clean-up - * gracefully on system's shutdown. - * - * NOTES: - * 1) Only provides a chance to clean-up on an opt-in basis. This guarantees back-compat - * support for components that do not wish to handle such event. - */ - private void shutdownComponents() { - if (D) Log.d(TAG, "Shutting down components..."); - - LocationProviderInterface gpsProvider = mProvidersByName.get(LocationManager.GPS_PROVIDER); - if (gpsProvider != null && gpsProvider.isEnabled()) { - gpsProvider.disable(); - } - } - - /** * Makes a list of userids that are related to the current user. This is * relevant when using managed profiles. Otherwise the list only contains * the current user. * * @param currentUserId the current user, who might have an alter-ego. */ - void updateUserProfiles(int currentUserId) { + private void updateUserProfiles(int currentUserId) { int[] profileIds = mUserManager.getProfileIdsWithDisabled(currentUserId); synchronized (mLock) { mCurrentUserProfiles = profileIds; @@ -614,22 +579,28 @@ public class LocationManagerService extends ILocationManager.Stub { private void loadProvidersLocked() { // create a passive location provider, which is always enabled - PassiveProvider passiveProvider = new PassiveProvider(this); - addProviderLocked(passiveProvider); - mEnabledProviders.add(passiveProvider.getName()); + LocationProvider passiveProviderManager = new LocationProvider( + LocationManager.PASSIVE_PROVIDER); + PassiveProvider passiveProvider = new PassiveProvider(passiveProviderManager); + + addProviderLocked(passiveProviderManager); mPassiveProvider = passiveProvider; if (GnssLocationProvider.isSupported()) { // Create a gps location provider - GnssLocationProvider gnssProvider = new GnssLocationProvider(mContext, this, + LocationProvider gnssProviderManager = new LocationProvider( + LocationManager.GPS_PROVIDER); + GnssLocationProvider gnssProvider = new GnssLocationProvider(mContext, + gnssProviderManager, mLocationHandler.getLooper()); + mGnssSystemInfoProvider = gnssProvider.getGnssSystemInfoProvider(); mGnssBatchingProvider = gnssProvider.getGnssBatchingProvider(); mGnssMetricsProvider = gnssProvider.getGnssMetricsProvider(); mGnssStatusProvider = gnssProvider.getGnssStatusProvider(); mNetInitiatedListener = gnssProvider.getNetInitiatedListener(); - addProviderLocked(gnssProvider); - mRealProviders.put(LocationManager.GPS_PROVIDER, gnssProvider); + addProviderLocked(gnssProviderManager); + mRealProviders.put(LocationManager.GPS_PROVIDER, gnssProviderManager); mGnssMeasurementsProvider = gnssProvider.getGnssMeasurementsProvider(); mGnssNavigationMessageProvider = gnssProvider.getGnssNavigationMessageProvider(); mGpsGeofenceProxy = gnssProvider.getGpsGeofenceProxy(); @@ -657,34 +628,38 @@ public class LocationManagerService extends ILocationManager.Stub { ensureFallbackFusedProviderPresentLocked(pkgs); // bind to network provider + + LocationProvider networkProviderManager = new LocationProvider( + LocationManager.NETWORK_PROVIDER); LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( mContext, - LocationManager.NETWORK_PROVIDER, + networkProviderManager, NETWORK_LOCATION_SERVICE_ACTION, com.android.internal.R.bool.config_enableNetworkLocationOverlay, com.android.internal.R.string.config_networkLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (networkProvider != null) { - mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProvider); + mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProviderManager); mProxyProviders.add(networkProvider); - addProviderLocked(networkProvider); + addProviderLocked(networkProviderManager); } else { Slog.w(TAG, "no network location provider found"); } // bind to fused provider - LocationProviderProxy fusedLocationProvider = LocationProviderProxy.createAndBind( + LocationProvider fusedProviderManager = new LocationProvider( + LocationManager.FUSED_PROVIDER); + LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind( mContext, - LocationManager.FUSED_PROVIDER, + fusedProviderManager, FUSED_LOCATION_SERVICE_ACTION, com.android.internal.R.bool.config_enableFusedLocationOverlay, com.android.internal.R.string.config_fusedLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); - if (fusedLocationProvider != null) { - addProviderLocked(fusedLocationProvider); - mProxyProviders.add(fusedLocationProvider); - mEnabledProviders.add(fusedLocationProvider.getName()); - mRealProviders.put(LocationManager.FUSED_PROVIDER, fusedLocationProvider); + if (fusedProvider != null) { + addProviderLocked(fusedProviderManager); + mProxyProviders.add(fusedProvider); + mRealProviders.put(LocationManager.FUSED_PROVIDER, fusedProviderManager); } else { Slog.e(TAG, "no fused location provider found", new IllegalStateException("Location service needs a fused location provider")); @@ -765,12 +740,9 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (mLock) { mLastLocation.clear(); mLastLocationCoarseInterval.clear(); - for (LocationProviderInterface p : mProviders) { - updateProviderListenersLocked(p.getName(), false); - } - mCurrentUserId = userId; updateUserProfiles(userId); - updateProvidersLocked(); + updateProvidersSettingsLocked(); + mCurrentUserId = userId; } } @@ -786,6 +758,165 @@ public class LocationManagerService extends ILocationManager.Stub { } } + private class LocationProvider implements AbstractLocationProvider.LocationProviderManager { + + private final String mName; + private AbstractLocationProvider mProvider; + + // whether the provider is enabled in location settings + private boolean mSettingsEnabled; + + // whether the provider considers itself enabled + private volatile boolean mEnabled; + + @Nullable + private volatile ProviderProperties mProperties; + + private LocationProvider(String name) { + mName = name; + // TODO: initialize settings enabled? + } + + @Override + public void onAttachProvider(AbstractLocationProvider provider, boolean initiallyEnabled) { + checkState(mProvider == null); + + // the provider is not yet fully constructed at this point, so we may not do anything + // except save a reference for later use here. do not call any provider methods. + mProvider = provider; + mEnabled = initiallyEnabled; + mProperties = null; + } + + public String getName() { + return mName; + } + + public boolean isEnabled() { + return mSettingsEnabled && mEnabled; + } + + @Nullable + public ProviderProperties getProperties() { + return mProperties; + } + + public void setRequest(ProviderRequest request, WorkSource workSource) { + mProvider.setRequest(request, workSource); + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(mName + " provider:"); + pw.println(" setting=" + mSettingsEnabled); + pw.println(" enabled=" + mEnabled); + pw.println(" properties=" + mProperties); + mProvider.dump(fd, pw, args); + } + + public long getStatusUpdateTime() { + return mProvider.getStatusUpdateTime(); + } + + public int getStatus(Bundle extras) { + return mProvider.getStatus(extras); + } + + public void sendExtraCommand(String command, Bundle extras) { + mProvider.sendExtraCommand(command, extras); + } + + // called from any thread + @Override + public void onReportLocation(Location location) { + runOnHandler(() -> LocationManagerService.this.reportLocation(location, + mProvider == mPassiveProvider)); + } + + // called from any thread + @Override + public void onReportLocation(List<Location> locations) { + runOnHandler(() -> LocationManagerService.this.reportLocationBatch(locations)); + } + + // called from any thread + @Override + public void onSetEnabled(boolean enabled) { + runOnHandler(() -> { + if (enabled == mEnabled) { + return; + } + + mEnabled = enabled; + + if (!mSettingsEnabled) { + // this provider was disabled in settings anyways, so a change to it's own + // enabled status won't have any affect. + return; + } + + // traditionally clients can listen for changes to the LOCATION_PROVIDERS_ALLOWED + // setting to detect when providers are enabled or disabled (even though they aren't + // supposed to). to continue to support this we must force a change to this setting. + // we use the fused provider because this is forced to be always enabled in settings + // anyways, and so won't have any visible effect beyond triggering content observers + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + "+" + LocationManager.FUSED_PROVIDER, mCurrentUserId); + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + "-" + LocationManager.FUSED_PROVIDER, mCurrentUserId); + + synchronized (mLock) { + if (!enabled) { + // If any provider has been disabled, clear all last locations for all + // providers. This is to be on the safe side in case a provider has location + // derived from this disabled provider. + mLastLocation.clear(); + mLastLocationCoarseInterval.clear(); + } + + updateProviderListenersLocked(mName); + } + + mContext.sendBroadcastAsUser(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION), + UserHandle.ALL); + }); + } + + @Override + public void onSetProperties(ProviderProperties properties) { + runOnHandler(() -> mProperties = properties); + } + + private void setSettingsEnabled(boolean enabled) { + synchronized (mLock) { + if (mSettingsEnabled == enabled) { + return; + } + + mSettingsEnabled = enabled; + if (!mSettingsEnabled) { + // if any provider has been disabled, clear all last locations for all + // providers. this is to be on the safe side in case a provider has location + // derived from this disabled provider. + mLastLocation.clear(); + mLastLocationCoarseInterval.clear(); + updateProviderListenersLocked(mName); + } else if (mEnabled) { + updateProviderListenersLocked(mName); + } + } + } + + private void runOnHandler(Runnable runnable) { + if (Looper.myLooper() == mLocationHandler.getLooper()) { + runnable.run(); + } else { + mLocationHandler.post(runnable); + } + } + } + /** * A wrapper class holding either an ILocationListener or a PendingIntent to receive * location updates. @@ -793,24 +924,24 @@ public class LocationManagerService extends ILocationManager.Stub { private final class Receiver implements IBinder.DeathRecipient, PendingIntent.OnFinished { private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000; final Identity mIdentity; - final int mAllowedResolutionLevel; // resolution level allowed to receiver + private final int mAllowedResolutionLevel; // resolution level allowed to receiver - final ILocationListener mListener; + private final ILocationListener mListener; final PendingIntent mPendingIntent; final WorkSource mWorkSource; // WorkSource for battery blame, or null to assign to caller. - final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver. - final Object mKey; + private final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver. + private final Object mKey; final HashMap<String, UpdateRecord> mUpdateRecords = new HashMap<>(); // True if app ops has started monitoring this receiver for locations. - boolean mOpMonitoring; + private boolean mOpMonitoring; // True if app ops has started monitoring this receiver for high power (gps) locations. - boolean mOpHighPowerMonitoring; - int mPendingBroadcasts; + private boolean mOpHighPowerMonitoring; + private int mPendingBroadcasts; PowerManager.WakeLock mWakeLock; - Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid, + private Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) { mListener = listener; mPendingIntent = intent; @@ -885,9 +1016,10 @@ public class LocationManagerService extends ILocationManager.Stub { // See if receiver has any enabled update records. Also note if any update records // are high power (has a high power provider with an interval under a threshold). for (UpdateRecord updateRecord : mUpdateRecords.values()) { - if (isAllowedByCurrentUserSettingsLocked(updateRecord.mProvider)) { + if (isAllowedByUserSettingsLockedForUser(updateRecord.mProvider, + mCurrentUserId)) { requestingLocation = true; - LocationProviderInterface locationProvider + LocationManagerService.LocationProvider locationProvider = mProvidersByName.get(updateRecord.mProvider); ProviderProperties properties = locationProvider != null ? locationProvider.getProperties() : null; @@ -1034,7 +1166,7 @@ public class LocationManagerService extends ILocationManager.Stub { return true; } - public boolean callProviderEnabledLocked(String provider, boolean enabled) { + private boolean callProviderEnabledLocked(String provider, boolean enabled) { // First update AppOp monitoring. // An app may get/lose location access as providers are enabled/disabled. updateMonitoring(true); @@ -1236,7 +1368,7 @@ public class LocationManagerService extends ILocationManager.Stub { private class LinkedCallback implements IBinder.DeathRecipient { private final IBatchedLocationCallback mCallback; - public LinkedCallback(@NonNull IBatchedLocationCallback callback) { + private LinkedCallback(@NonNull IBatchedLocationCallback callback) { mCallback = callback; } @@ -1337,7 +1469,7 @@ public class LocationManagerService extends ILocationManager.Stub { checkCallerIsProvider(); // Currently used only for GNSS locations - update permissions check if changed - if (isAllowedByCurrentUserSettingsLocked(LocationManager.GPS_PROVIDER)) { + if (isAllowedByUserSettingsLockedForUser(LocationManager.GPS_PROVIDER, mCurrentUserId)) { if (mGnssBatchingCallback == null) { Slog.e(TAG, "reportLocationBatch() called without active Callback"); return; @@ -1352,29 +1484,17 @@ public class LocationManagerService extends ILocationManager.Stub { } } - private void addProviderLocked(LocationProviderInterface provider) { + private void addProviderLocked(LocationProvider provider) { mProviders.add(provider); mProvidersByName.put(provider.getName(), provider); } - private void removeProviderLocked(LocationProviderInterface provider) { - provider.disable(); + private void removeProviderLocked(LocationProvider provider) { mProviders.remove(provider); mProvidersByName.remove(provider.getName()); } /** - * Returns "true" if access to the specified location provider is allowed by the current - * user's settings. Access to all location providers is forbidden to non-location-provider - * processes belonging to background users. - * - * @param provider the name of the location provider - */ - private boolean isAllowedByCurrentUserSettingsLocked(String provider) { - return isAllowedByUserSettingsLockedForUser(provider, mCurrentUserId); - } - - /** * Returns "true" if access to the specified location provider is allowed by the specified * user's settings. Access to all location providers is forbidden to non-location-provider * processes belonging to background users. @@ -1383,13 +1503,28 @@ public class LocationManagerService extends ILocationManager.Stub { * @param userId the user id to query */ private boolean isAllowedByUserSettingsLockedForUser(String provider, int userId) { - if (mEnabledProviders.contains(provider)) { - return true; + if (LocationManager.PASSIVE_PROVIDER.equals(provider)) { + return isLocationEnabledForUser(userId); } - if (mDisabledProviders.contains(provider)) { - return false; + if (LocationManager.FUSED_PROVIDER.equals(provider)) { + return isLocationEnabledForUser(userId); + } + synchronized (mLock) { + if (mMockProviders.containsKey(provider)) { + return isLocationEnabledForUser(userId); + } + } + + long identity = Binder.clearCallingIdentity(); + try { + // Use system settings + ContentResolver cr = mContext.getContentResolver(); + String allowedProviders = Settings.Secure.getStringForUser( + cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, userId); + return TextUtils.delimitedStringContains(allowedProviders, ',', provider); + } finally { + Binder.restoreCallingIdentity(identity); } - return isLocationProviderEnabledForUser(provider, userId); } @@ -1481,9 +1616,11 @@ public class LocationManagerService extends ILocationManager.Stub { // network and fused providers are ok with COARSE or FINE return RESOLUTION_LEVEL_COARSE; } else { - // mock providers - LocationProviderInterface lp = mMockProviders.get(provider); - if (lp != null) { + for (LocationProvider lp : mProviders) { + if (!lp.getName().equals(provider)) { + continue; + } + ProviderProperties properties = lp.getProperties(); if (properties != null) { if (properties.mRequiresSatellite) { @@ -1496,6 +1633,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } } + return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE } @@ -1550,7 +1688,7 @@ public class LocationManagerService extends ILocationManager.Stub { } private static String resolutionLevelToOpStr(int allowedResolutionLevel) { - switch(allowedResolutionLevel) { + switch (allowedResolutionLevel) { case RESOLUTION_LEVEL_COARSE: return AppOpsManager.OPSTR_COARSE_LOCATION; case RESOLUTION_LEVEL_FINE: @@ -1565,7 +1703,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } - boolean reportLocationAccessNoThrow( + private boolean reportLocationAccessNoThrow( int pid, int uid, String packageName, int allowedResolutionLevel) { int op = resolutionLevelToOp(allowedResolutionLevel); if (op >= 0) { @@ -1577,7 +1715,8 @@ public class LocationManagerService extends ILocationManager.Stub { return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel; } - boolean checkLocationAccess(int pid, int uid, String packageName, int allowedResolutionLevel) { + private boolean checkLocationAccess(int pid, int uid, String packageName, + int allowedResolutionLevel) { int op = resolutionLevelToOp(allowedResolutionLevel); if (op >= 0) { if (mAppOps.noteOp(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) { @@ -1597,7 +1736,7 @@ public class LocationManagerService extends ILocationManager.Stub { ArrayList<String> out; synchronized (mLock) { out = new ArrayList<>(mProviders.size()); - for (LocationProviderInterface provider : mProviders) { + for (LocationProvider provider : mProviders) { String name = provider.getName(); if (LocationManager.FUSED_PROVIDER.equals(name)) { continue; @@ -1623,7 +1762,7 @@ public class LocationManagerService extends ILocationManager.Stub { try { synchronized (mLock) { out = new ArrayList<>(mProviders.size()); - for (LocationProviderInterface provider : mProviders) { + for (LocationProvider provider : mProviders) { String name = provider.getName(); if (LocationManager.FUSED_PROVIDER.equals(name)) { continue; @@ -1633,7 +1772,8 @@ public class LocationManagerService extends ILocationManager.Stub { && !isAllowedByUserSettingsLocked(name, uid, mCurrentUserId)) { continue; } - if (criteria != null && !LocationProvider.propertiesMeetCriteria( + if (criteria != null + && !android.location.LocationProvider.propertiesMeetCriteria( name, provider.getProperties(), criteria)) { continue; } @@ -1658,7 +1798,7 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public String getBestProvider(Criteria criteria, boolean enabledOnly) { - String result = null; + String result; List<String> providers = getProviders(criteria, enabledOnly); if (!providers.isEmpty()) { @@ -1673,7 +1813,7 @@ public class LocationManagerService extends ILocationManager.Stub { return result; } - if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); + if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + null); return null; } @@ -1689,51 +1829,32 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean providerMeetsCriteria(String provider, Criteria criteria) { - LocationProviderInterface p = mProvidersByName.get(provider); + LocationProvider p = mProvidersByName.get(provider); if (p == null) { throw new IllegalArgumentException("provider=" + provider); } - boolean result = LocationProvider.propertiesMeetCriteria( + boolean result = android.location.LocationProvider.propertiesMeetCriteria( p.getName(), p.getProperties(), criteria); if (D) Log.d(TAG, "providerMeetsCriteria(" + provider + ", " + criteria + ")=" + result); return result; } - private void updateProvidersLocked() { - boolean changesMade = false; - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface p = mProviders.get(i); - boolean isEnabled = p.isEnabled(); - String name = p.getName(); - boolean shouldBeEnabled = isAllowedByCurrentUserSettingsLocked(name); - if (isEnabled && !shouldBeEnabled) { - updateProviderListenersLocked(name, false); - // If any provider has been disabled, clear all last locations for all providers. - // This is to be on the safe side in case a provider has location derived from - // this disabled provider. - mLastLocation.clear(); - mLastLocationCoarseInterval.clear(); - changesMade = true; - } else if (!isEnabled && shouldBeEnabled) { - updateProviderListenersLocked(name, true); - changesMade = true; - } + private void updateProvidersSettingsLocked() { + for (LocationProvider p : mProviders) { + p.setSettingsEnabled(isAllowedByUserSettingsLockedForUser(p.getName(), mCurrentUserId)); } - if (changesMade) { - mContext.sendBroadcastAsUser(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION), - UserHandle.ALL); - mContext.sendBroadcastAsUser(new Intent(LocationManager.MODE_CHANGED_ACTION), - UserHandle.ALL); - } - } - private void updateProviderListenersLocked(String provider, boolean enabled) { - int listeners = 0; + mContext.sendBroadcastAsUser(new Intent(LocationManager.MODE_CHANGED_ACTION), + UserHandle.ALL); + } - LocationProviderInterface p = mProvidersByName.get(provider); + private void updateProviderListenersLocked(String provider) { + LocationProvider p = mProvidersByName.get(provider); if (p == null) return; + boolean enabled = p.isEnabled(); + ArrayList<Receiver> deadReceivers = null; ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); @@ -1747,7 +1868,6 @@ public class LocationManagerService extends ILocationManager.Stub { } deadReceivers.add(record.mReceiver); } - listeners++; } } } @@ -1758,16 +1878,11 @@ public class LocationManagerService extends ILocationManager.Stub { } } - if (enabled) { - p.enable(); - applyRequirementsLocked(provider); - } else { - p.disable(); - } + applyRequirementsLocked(provider); } private void applyRequirementsLocked(String provider) { - LocationProviderInterface p = mProvidersByName.get(provider); + LocationProvider p = mProvidersByName.get(provider); if (p == null) return; ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); @@ -1779,10 +1894,10 @@ public class LocationManagerService extends ILocationManager.Stub { resolver, Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS); - // initialize the low power mode to true and set to false if any of the records requires - providerRequest.lowPowerMode = true; - if (records != null) { + if (p.isEnabled() && records != null && !records.isEmpty()) { + // initialize the low power mode to true and set to false if any of the records requires + providerRequest.lowPowerMode = true; for (UpdateRecord record : records) { if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { if (checkLocationAccess( @@ -1875,7 +1990,7 @@ public class LocationManagerService extends ILocationManager.Stub { public String[] getBackgroundThrottlingWhitelist() { synchronized (mLock) { return mBackgroundThrottlePackageWhitelist.toArray( - new String[mBackgroundThrottlePackageWhitelist.size()]); + new String[0]); } } @@ -1922,17 +2037,17 @@ public class LocationManagerService extends ILocationManager.Stub { private class UpdateRecord { final String mProvider; - final LocationRequest mRealRequest; // original request from client + private final LocationRequest mRealRequest; // original request from client LocationRequest mRequest; // possibly throttled version of the request - final Receiver mReceiver; - boolean mIsForegroundUid; - Location mLastFixBroadcast; - long mLastStatusBroadcast; + private final Receiver mReceiver; + private boolean mIsForegroundUid; + private Location mLastFixBroadcast; + private long mLastStatusBroadcast; /** * Note: must be constructed with lock held. */ - UpdateRecord(String provider, LocationRequest request, Receiver receiver) { + private UpdateRecord(String provider, LocationRequest request, Receiver receiver) { mProvider = provider; mRealRequest = request; mRequest = request; @@ -1958,7 +2073,7 @@ public class LocationManagerService extends ILocationManager.Stub { /** * Method to be called when record changes foreground/background */ - void updateForeground(boolean isForeground){ + private void updateForeground(boolean isForeground) { mIsForegroundUid = isForeground; mRequestStatistics.updateForeground( mReceiver.mIdentity.mPackageName, mProvider, isForeground); @@ -1967,7 +2082,7 @@ public class LocationManagerService extends ILocationManager.Stub { /** * Method to be called when a record will no longer be used. */ - void disposeLocked(boolean removeReceiver) { + private void disposeLocked(boolean removeReceiver) { mRequestStatistics.stopRequesting(mReceiver.mIdentity.mPackageName, mProvider); // remove from mRecordsByProvider @@ -1980,13 +2095,11 @@ public class LocationManagerService extends ILocationManager.Stub { // remove from Receiver#mUpdateRecords HashMap<String, UpdateRecord> receiverRecords = mReceiver.mUpdateRecords; - if (receiverRecords != null) { - receiverRecords.remove(this.mProvider); + receiverRecords.remove(this.mProvider); - // and also remove the Receiver if it has no more update records - if (receiverRecords.size() == 0) { - removeUpdatesLocked(mReceiver); - } + // and also remove the Receiver if it has no more update records + if (receiverRecords.size() == 0) { + removeUpdatesLocked(mReceiver); } } @@ -2070,7 +2183,7 @@ public class LocationManagerService extends ILocationManager.Stub { private void checkPackageName(String packageName) { if (packageName == null) { - throw new SecurityException("invalid package name: " + packageName); + throw new SecurityException("invalid package name: " + null); } int uid = Binder.getCallingUid(); String[] packages = mPackageManager.getPackagesForUid(uid); @@ -2085,7 +2198,7 @@ public class LocationManagerService extends ILocationManager.Stub { private void checkPendingIntent(PendingIntent intent) { if (intent == null) { - throw new IllegalArgumentException("invalid pending intent: " + intent); + throw new IllegalArgumentException("invalid pending intent: " + null); } } @@ -2137,7 +2250,7 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (mLock) { Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid, packageName, workSource, hideFromAppOps); - requestLocationUpdatesLocked(sanitizedRequest, recevier, pid, uid, packageName); + requestLocationUpdatesLocked(sanitizedRequest, recevier, uid, packageName); } } finally { Binder.restoreCallingIdentity(identity); @@ -2145,7 +2258,7 @@ public class LocationManagerService extends ILocationManager.Stub { } private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver, - int pid, int uid, String packageName) { + int uid, String packageName) { // Figure out the provider. Either its explicitly request (legacy use cases), or // use the fused provider if (request == null) request = DEFAULT_LOCATION_REQUEST; @@ -2154,7 +2267,7 @@ public class LocationManagerService extends ILocationManager.Stub { throw new IllegalArgumentException("provider name must not be null"); } - LocationProviderInterface provider = mProvidersByName.get(name); + LocationProvider provider = mProvidersByName.get(name); if (provider == null) { throw new IllegalArgumentException("provider doesn't exist: " + name); } @@ -2173,8 +2286,7 @@ public class LocationManagerService extends ILocationManager.Stub { oldRecord.disposeLocked(false); } - boolean isProviderEnabled = isAllowedByUserSettingsLocked(name, uid, mCurrentUserId); - if (isProviderEnabled) { + if (provider.isEnabled()) { applyRequirementsLocked(name); } else { // Notify the listener that updates are currently disabled @@ -2194,10 +2306,8 @@ public class LocationManagerService extends ILocationManager.Stub { final int uid = Binder.getCallingUid(); synchronized (mLock) { - WorkSource workSource = null; - boolean hideFromAppOps = false; Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid, - packageName, workSource, hideFromAppOps); + packageName, null, false); // providers may use public location API's, need to clear identity long identity = Binder.clearCallingIdentity(); @@ -2236,22 +2346,12 @@ public class LocationManagerService extends ILocationManager.Stub { // update provider for (String provider : providers) { - // If provider is already disabled, don't need to do anything - if (!isAllowedByCurrentUserSettingsLocked(provider)) { - continue; - } - applyRequirementsLocked(provider); } } private void applyAllProviderRequirementsLocked() { - for (LocationProviderInterface p : mProviders) { - // If provider is already disabled, don't need to do anything - if (!isAllowedByCurrentUserSettingsLocked(p.getName())) { - continue; - } - + for (LocationProvider p : mProviders) { applyRequirementsLocked(p.getName()); } } @@ -2291,7 +2391,7 @@ public class LocationManagerService extends ILocationManager.Stub { // or use the fused provider String name = request.getProvider(); if (name == null) name = LocationManager.FUSED_PROVIDER; - LocationProviderInterface provider = mProvidersByName.get(name); + LocationProvider provider = mProvidersByName.get(name); if (provider == null) return null; if (!isAllowedByUserSettingsLocked(name, uid, mCurrentUserId)) return null; @@ -2314,7 +2414,7 @@ public class LocationManagerService extends ILocationManager.Stub { location.getElapsedRealtimeNanos() / NANOS_PER_MILLI; if ((locationAgeMs > mLastLocationMaxAgeMs) && (mAppOps.unsafeCheckOp(op, uid, packageName) - == AppOpsManager.MODE_FOREGROUND)) { + == AppOpsManager.MODE_FOREGROUND)) { return null; } @@ -2358,7 +2458,7 @@ public class LocationManagerService extends ILocationManager.Stub { } return false; } - LocationProviderInterface p = null; + LocationProvider p = null; String provider = location.getProvider(); if (provider != null) { p = mProvidersByName.get(provider); @@ -2370,7 +2470,7 @@ public class LocationManagerService extends ILocationManager.Stub { return false; } synchronized (mLock) { - if (!isAllowedByCurrentUserSettingsLocked(provider)) { + if (!isAllowedByUserSettingsLockedForUser(provider, mCurrentUserId)) { if (D) { Log.d(TAG, "Location disabled in Settings for current user:" + mCurrentUserId); } @@ -2444,31 +2544,20 @@ public class LocationManagerService extends ILocationManager.Stub { } } - @Override public boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName) { if (!hasGnssPermissions(packageName) || mGnssStatusProvider == null) { return false; } - try { - mGnssStatusProvider.registerGnssStatusCallback(callback); - } catch (RemoteException e) { - Slog.e(TAG, "mGpsStatusProvider.registerGnssStatusCallback failed", e); - return false; - } - return true; + // TODO(b/120449926): The GNSS status listeners should be handled similar to the GNSS + // measurements listeners. + return mGnssStatusProvider.addListener(callback, Binder.getCallingUid(), packageName); } @Override public void unregisterGnssStatusCallback(IGnssStatusListener callback) { - synchronized (mLock) { - try { - mGnssStatusProvider.unregisterGnssStatusCallback(callback); - } catch (Exception e) { - Slog.e(TAG, "mGpsStatusProvider.unregisterGnssStatusCallback failed", e); - } - } + mGnssStatusProvider.removeListener(callback); } @Override @@ -2481,13 +2570,15 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (mLock) { Identity callerIdentity = new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName); + // TODO(b/120481270): Register for client death notification and update map. mGnssMeasurementsListeners.put(listener.asBinder(), callerIdentity); long identity = Binder.clearCallingIdentity(); try { if (isThrottlingExemptLocked(callerIdentity) || isImportanceForeground( mActivityManager.getPackageImportance(packageName))) { - return mGnssMeasurementsProvider.addListener(listener); + return mGnssMeasurementsProvider.addListener(listener, + callerIdentity.mUid, callerIdentity.mPackageName); } } finally { Binder.restoreCallingIdentity(identity); @@ -2499,11 +2590,13 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void removeGnssMeasurementsListener(IGnssMeasurementsListener listener) { - if (mGnssMeasurementsProvider != null) { - synchronized (mLock) { - mGnssMeasurementsListeners.remove(listener.asBinder()); - mGnssMeasurementsProvider.removeListener(listener); - } + if (mGnssMeasurementsProvider == null) { + return; + } + + synchronized (mLock) { + mGnssMeasurementsListeners.remove(listener.asBinder()); + mGnssMeasurementsProvider.removeListener(listener); } } @@ -2518,13 +2611,15 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (mLock) { Identity callerIdentity = new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName); + // TODO(b/120481270): Register for client death notification and update map. mGnssNavigationMessageListeners.put(listener.asBinder(), callerIdentity); long identity = Binder.clearCallingIdentity(); try { if (isThrottlingExemptLocked(callerIdentity) || isImportanceForeground( mActivityManager.getPackageImportance(packageName))) { - return mGnssNavigationMessageProvider.addListener(listener); + return mGnssNavigationMessageProvider.addListener(listener, + callerIdentity.mUid, callerIdentity.mPackageName); } } finally { Binder.restoreCallingIdentity(identity); @@ -2560,10 +2655,11 @@ public class LocationManagerService extends ILocationManager.Stub { } synchronized (mLock) { - LocationProviderInterface p = mProvidersByName.get(provider); + LocationProvider p = mProvidersByName.get(provider); if (p == null) return false; - return p.sendExtraCommand(command, extras); + p.sendExtraCommand(command, extras); + return true; } } @@ -2588,14 +2684,10 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public ProviderProperties getProviderProperties(String provider) { - if (mProvidersByName.get(provider) == null) { - return null; - } - checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), provider); - LocationProviderInterface p; + LocationProvider p; synchronized (mLock) { p = mProvidersByName.get(provider); } @@ -2611,25 +2703,25 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public String getNetworkProviderPackage() { - LocationProviderInterface p; + LocationProvider p; synchronized (mLock) { - if (mProvidersByName.get(LocationManager.NETWORK_PROVIDER) == null) { - return null; - } p = mProvidersByName.get(LocationManager.NETWORK_PROVIDER); } - if (p instanceof LocationProviderProxy) { - return ((LocationProviderProxy) p).getConnectedPackageName(); + if (p == null) { + return null; + } + if (p.mProvider instanceof LocationProviderProxy) { + return ((LocationProviderProxy) p.mProvider).getConnectedPackageName(); } return null; } /** - * Returns the current location enabled/disabled status for a user + * Returns the current location enabled/disabled status for a user * - * @param userId the id of the user - * @return true if location is enabled + * @param userId the id of the user + * @return true if location is enabled */ @Override public boolean isLocationEnabledForUser(int userId) { @@ -2647,7 +2739,7 @@ public class LocationManagerService extends ILocationManager.Stub { return false; } final List<String> providerList = Arrays.asList(allowedProviders.split(",")); - for(String provider : mRealProviders.keySet()) { + for (String provider : mRealProviders.keySet()) { if (provider.equals(LocationManager.PASSIVE_PROVIDER) || provider.equals(LocationManager.FUSED_PROVIDER)) { continue; @@ -2664,16 +2756,16 @@ public class LocationManagerService extends ILocationManager.Stub { } /** - * Enable or disable location for a user + * Enable or disable location for a user * - * @param enabled true to enable location, false to disable location - * @param userId the id of the user + * @param enabled true to enable location, false to disable location + * @param userId the id of the user */ @Override public void setLocationEnabledForUser(boolean enabled, int userId) { mContext.enforceCallingPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS, - "Requires WRITE_SECURE_SETTINGS permission"); + android.Manifest.permission.WRITE_SECURE_SETTINGS, + "Requires WRITE_SECURE_SETTINGS permission"); // Check INTERACT_ACROSS_USERS permission if userId is not current user id. checkInteractAcrossUsersPermission(userId); @@ -2688,7 +2780,7 @@ public class LocationManagerService extends ILocationManager.Stub { allProvidersSet.addAll(allRealProviders); // When disabling location, disable gps and network provider that could have been // enabled by location mode api. - if (enabled == false) { + if (!enabled) { allProvidersSet.add(LocationManager.GPS_PROVIDER); allProvidersSet.add(LocationManager.NETWORK_PROVIDER); } @@ -2723,93 +2815,48 @@ public class LocationManagerService extends ILocationManager.Stub { } /** - * Returns the current enabled/disabled status of a location provider and user + * Returns the current enabled/disabled status of a location provider and user * - * @param provider name of the provider - * @param userId the id of the user - * @return true if the provider exists and is enabled + * @param providerName name of the provider + * @param userId the id of the user + * @return true if the provider exists and is enabled */ @Override - public boolean isProviderEnabledForUser(String provider, int userId) { + public boolean isProviderEnabledForUser(String providerName, int userId) { // Check INTERACT_ACROSS_USERS permission if userId is not current user id. checkInteractAcrossUsersPermission(userId); - // Fused provider is accessed indirectly via criteria rather than the provider-based APIs, - // so we discourage its use - if (LocationManager.FUSED_PROVIDER.equals(provider)) return false; - - int uid = Binder.getCallingUid(); - synchronized (mLock) { - LocationProviderInterface p = mProvidersByName.get(provider); - return p != null - && isAllowedByUserSettingsLocked(provider, uid, userId); + if (!isLocationEnabledForUser(userId)) { + return false; } - } - - /** - * Enable or disable a single location provider. - * - * @param provider name of the provider - * @param enabled true to enable the provider. False to disable the provider - * @param userId the id of the user to set - * @return true if the value was set, false on errors - */ - @Override - public boolean setProviderEnabledForUser(String provider, boolean enabled, int userId) { - mContext.enforceCallingPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS, - "Requires WRITE_SECURE_SETTINGS permission"); - - // Check INTERACT_ACROSS_USERS permission if userId is not current user id. - checkInteractAcrossUsersPermission(userId); // Fused provider is accessed indirectly via criteria rather than the provider-based APIs, // so we discourage its use - if (LocationManager.FUSED_PROVIDER.equals(provider)) return false; + if (LocationManager.FUSED_PROVIDER.equals(providerName)) return false; long identity = Binder.clearCallingIdentity(); try { + LocationProvider provider; synchronized (mLock) { - // No such provider exists - if (!mProvidersByName.containsKey(provider)) return false; - - // If it is a test provider, do not write to Settings.Secure - if (mMockProviders.containsKey(provider)) { - setTestProviderEnabled(provider, enabled); - return true; - } - - // to ensure thread safety, we write the provider name with a '+' or '-' - // and let the SettingsProvider handle it rather than reading and modifying - // the list of enabled providers. - String providerChange = (enabled ? "+" : "-") + provider; - return Settings.Secure.putStringForUser( - mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - providerChange, userId); + provider = mProvidersByName.get(providerName); } + return provider != null && provider.isEnabled(); } finally { Binder.restoreCallingIdentity(identity); } } /** - * Read location provider status from Settings.Secure + * Enable or disable a single location provider. * - * @param provider the location provider to query - * @param userId the user id to query - * @return true if the provider is enabled + * @param provider name of the provider + * @param enabled true to enable the provider. False to disable the provider + * @param userId the id of the user to set + * @return true if the value was set, false on errors */ - private boolean isLocationProviderEnabledForUser(String provider, int userId) { - long identity = Binder.clearCallingIdentity(); - try { - // Use system settings - ContentResolver cr = mContext.getContentResolver(); - String allowedProviders = Settings.Secure.getStringForUser( - cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, userId); - return TextUtils.delimitedStringContains(allowedProviders, ',', provider); - } finally { - Binder.restoreCallingIdentity(identity); - } + @Override + public boolean setProviderEnabledForUser(String provider, boolean enabled, int userId) { + return false; } /** @@ -2941,7 +2988,7 @@ public class LocationManagerService extends ILocationManager.Stub { long now = SystemClock.elapsedRealtime(); String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider()); // Skip if the provider is unknown. - LocationProviderInterface p = mProvidersByName.get(provider); + LocationProvider p = mProvidersByName.get(provider); if (p == null) return; updateLastLocationLocked(location, provider); // mLastLocation should have been updated from the updateLastLocationLocked call above. @@ -3053,7 +3100,7 @@ public class LocationManagerService extends ILocationManager.Stub { LOCATION_DISABLE_STATUS_CALLBACKS, 1) == 0) { long prevStatusUpdateTime = r.mLastStatusBroadcast; if ((newStatusUpdateTime > prevStatusUpdateTime) - && (prevStatusUpdateTime != 0 || status != LocationProvider.AVAILABLE)) { + && (prevStatusUpdateTime != 0 || status != AVAILABLE)) { r.mLastStatusBroadcast = newStatusUpdateTime; if (!receiver.callStatusChangedLocked(provider, status, extras)) { @@ -3098,8 +3145,8 @@ public class LocationManagerService extends ILocationManager.Stub { /** * Updates last location with the given location * - * @param location new location to update - * @param provider Location provider to update for + * @param location new location to update + * @param provider Location provider to update for */ private void updateLastLocationLocked(Location location, String provider) { Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); @@ -3154,13 +3201,11 @@ public class LocationManagerService extends ILocationManager.Stub { } synchronized (mLock) { - if (isAllowedByCurrentUserSettingsLocked(provider)) { - if (!passive) { - // notify passive provider of the new location - mPassiveProvider.updateLocation(myLocation); - } - handleLocationChangedLocked(myLocation, passive); + if (!passive) { + // notify passive provider of the new location + mPassiveProvider.updateLocation(myLocation); } + handleLocationChangedLocked(myLocation, passive); } } @@ -3245,13 +3290,12 @@ public class LocationManagerService extends ILocationManager.Stub { if (LocationManager.GPS_PROVIDER.equals(name) || LocationManager.NETWORK_PROVIDER.equals(name) || LocationManager.FUSED_PROVIDER.equals(name)) { - LocationProviderInterface p = mProvidersByName.get(name); + LocationProvider p = mProvidersByName.get(name); if (p != null) { removeProviderLocked(p); } } addTestProviderLocked(name, properties); - updateProvidersLocked(); } Binder.restoreCallingIdentity(identity); } @@ -3260,9 +3304,12 @@ public class LocationManagerService extends ILocationManager.Stub { if (mProvidersByName.get(name) != null) { throw new IllegalArgumentException("Provider \"" + name + "\" already exists"); } - MockProvider provider = new MockProvider(name, this, properties); + + LocationProvider provider = new LocationProvider(name); + MockProvider mockProvider = new MockProvider(provider, properties); + addProviderLocked(provider); - mMockProviders.put(name, provider); + mMockProviders.put(name, mockProvider); mLastLocation.put(name, null); mLastLocationCoarseInterval.put(name, null); } @@ -3274,28 +3321,25 @@ public class LocationManagerService extends ILocationManager.Stub { } synchronized (mLock) { - - // These methods can't be called after removing the test provider, so first make sure - // we don't leave anything dangling. - clearTestProviderEnabled(provider, opPackageName); - clearTestProviderLocation(provider, opPackageName); - MockProvider mockProvider = mMockProviders.remove(provider); if (mockProvider == null) { throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); } + long identity = Binder.clearCallingIdentity(); - removeProviderLocked(mProvidersByName.get(provider)); + try { + removeProviderLocked(mProvidersByName.get(provider)); - // reinstate real provider if available - LocationProviderInterface realProvider = mRealProviders.get(provider); - if (realProvider != null) { - addProviderLocked(realProvider); + // reinstate real provider if available + LocationProvider realProvider = mRealProviders.get(provider); + if (realProvider != null) { + addProviderLocked(realProvider); + } + mLastLocation.put(provider, null); + mLastLocationCoarseInterval.put(provider, null); + } finally { + Binder.restoreCallingIdentity(identity); } - mLastLocation.put(provider, null); - mLastLocationCoarseInterval.put(provider, null); - updateProvidersLocked(); - Binder.restoreCallingIdentity(identity); } } @@ -3325,23 +3369,11 @@ public class LocationManagerService extends ILocationManager.Stub { // clear calling identity so INSTALL_LOCATION_PROVIDER permission is not required long identity = Binder.clearCallingIdentity(); - mockProvider.setLocation(mock); - Binder.restoreCallingIdentity(identity); - } - } - - @Override - public void clearTestProviderLocation(String provider, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { - return; - } - - synchronized (mLock) { - MockProvider mockProvider = mMockProviders.get(provider); - if (mockProvider == null) { - throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); + try { + mockProvider.setLocation(mock); + } finally { + Binder.restoreCallingIdentity(identity); } - mockProvider.clearLocation(); } } @@ -3350,47 +3382,18 @@ public class LocationManagerService extends ILocationManager.Stub { if (!canCallerAccessMockLocation(opPackageName)) { return; } - setTestProviderEnabled(provider, enabled); - } - /** Enable or disable a test location provider. */ - private void setTestProviderEnabled(String provider, boolean enabled) { synchronized (mLock) { MockProvider mockProvider = mMockProviders.get(provider); if (mockProvider == null) { throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); } long identity = Binder.clearCallingIdentity(); - if (enabled) { - mockProvider.enable(); - mEnabledProviders.add(provider); - mDisabledProviders.remove(provider); - } else { - mockProvider.disable(); - mEnabledProviders.remove(provider); - mDisabledProviders.add(provider); - } - updateProvidersLocked(); - Binder.restoreCallingIdentity(identity); - } - } - - @Override - public void clearTestProviderEnabled(String provider, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { - return; - } - - synchronized (mLock) { - MockProvider mockProvider = mMockProviders.get(provider); - if (mockProvider == null) { - throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); + try { + mockProvider.setEnabled(enabled); + } finally { + Binder.restoreCallingIdentity(identity); } - long identity = Binder.clearCallingIdentity(); - mEnabledProviders.remove(provider); - mDisabledProviders.remove(provider); - updateProvidersLocked(); - Binder.restoreCallingIdentity(identity); } } @@ -3450,10 +3453,11 @@ public class LocationManagerService extends ILocationManager.Stub { + identity.mPackageName + ": " + isThrottlingExemptLocked(identity)); } pw.println(" Overlay Provider Packages:"); - for (LocationProviderInterface provider : mProviders) { - if (provider instanceof LocationProviderProxy) { + for (LocationProvider provider : mProviders) { + if (provider.mProvider instanceof LocationProviderProxy) { pw.println(" " + provider.getName() + ": " - + ((LocationProviderProxy) provider).getConnectedPackageName()); + + ((LocationProviderProxy) provider.mProvider) + .getConnectedPackageName()); } } pw.println(" Historical Records by Provider:"); @@ -3479,25 +3483,12 @@ public class LocationManagerService extends ILocationManager.Stub { mGeofenceManager.dump(pw); - if (mEnabledProviders.size() > 0) { - pw.println(" Enabled Providers:"); - for (String i : mEnabledProviders) { - pw.println(" " + i); - } - - } - if (mDisabledProviders.size() > 0) { - pw.println(" Disabled Providers:"); - for (String i : mDisabledProviders) { - pw.println(" " + i); - } - } pw.append(" "); mBlacklist.dump(pw); if (mMockProviders.size() > 0) { pw.println(" Mock Providers:"); for (Map.Entry<String, MockProvider> i : mMockProviders.entrySet()) { - i.getValue().dump(pw, " "); + i.getValue().dump(fd, pw, args); } } @@ -3514,13 +3505,7 @@ public class LocationManagerService extends ILocationManager.Stub { if (args.length > 0 && "short".equals(args[0])) { return; } - for (LocationProviderInterface provider : mProviders) { - pw.print(provider.getName() + " Internal State"); - if (provider instanceof LocationProviderProxy) { - LocationProviderProxy proxy = (LocationProviderProxy) provider; - pw.print(" (" + proxy.getConnectedPackageName() + ")"); - } - pw.println(":"); + for (LocationProvider provider : mProviders) { provider.dump(fd, pw, args); } if (mGnssBatchingInProgress) { diff --git a/services/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java index fa3babad639d..cee98c10c7f7 100644 --- a/services/core/java/com/android/server/LooperStatsService.java +++ b/services/core/java/com/android/server/LooperStatsService.java @@ -31,6 +31,7 @@ import android.text.format.DateFormat; import android.util.KeyValueListParser; import android.util.Slog; +import com.android.internal.os.AppIdToPackageMap; import com.android.internal.os.BackgroundThread; import com.android.internal.os.CachedDeviceState; import com.android.internal.os.LooperStats; @@ -92,6 +93,7 @@ public class LooperStatsService extends Binder { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + AppIdToPackageMap packageMap = AppIdToPackageMap.getSnapshot(); pw.print("Start time: "); pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStats.getStartTimeMillis())); pw.print("On battery time (ms): "); @@ -121,7 +123,7 @@ public class LooperStatsService extends Binder { pw.println(header); for (LooperStats.ExportedEntry entry : entries) { pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", - entry.workSourceUid, + packageMap.mapUid(entry.workSourceUid), entry.threadName, entry.handlerClassName, entry.messageName, diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 87a42fa45143..4678fec377d0 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -956,18 +956,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub // INetworkManagementService members // @Override - public INetd getNetdService() throws RemoteException { - final CountDownLatch connectedSignal = mConnectedSignal; - if (connectedSignal != null) { - try { - connectedSignal.await(); - } catch (InterruptedException ignored) {} - } - - return mNetdService; - } - - @Override public String[] listInterfaces() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java index 574f54a51eb1..d09823efb6fa 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -16,9 +16,7 @@ package com.android.server; -import android.annotation.MainThread; import android.annotation.Nullable; -import android.annotation.WorkerThread; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -168,13 +166,13 @@ public class ServiceWatcher implements ServiceConnection { // called on handler thread @GuardedBy("mBindLock") protected void onBind() { - + Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); } // called on handler thread @GuardedBy("mBindLock") protected void onUnbind() { - + Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); } /** @@ -205,12 +203,12 @@ public class ServiceWatcher implements ServiceConnection { @Override public void onPackageRemoved(String packageName, int uid) { - bindBestPackage(Objects.equals(packageName, getCurrentPackageName())); + bindBestPackage(Objects.equals(packageName, getCurrentPackageName())); } @Override public boolean onPackageChanged(String packageName, int uid, String[] components) { - bindBestPackage(Objects.equals(packageName, getCurrentPackageName())); + bindBestPackage(Objects.equals(packageName, getCurrentPackageName())); return super.onPackageChanged(packageName, uid, components); } }.register(mContext, UserHandle.ALL, true, mHandler); @@ -243,20 +241,16 @@ public class ServiceWatcher implements ServiceConnection { return true; } - /** Returns thje name of the currently connected package or null. */ + /** Returns the name of the currently connected package or null. */ @Nullable public String getCurrentPackageName() { ComponentName bestComponent = mBestComponent; return bestComponent == null ? null : bestComponent.getPackageName(); } - public int getCurrentPackageVersion() { - return mBestVersion; - } - /** - * Runs the given BinderRunner if currently connected. Returns true if it was run, and false - * otherwise. All invocations to runOnBinder are run serially. + * Runs the given BinderRunner if currently connected. All invocations to runOnBinder are run + * serially. */ public final void runOnBinder(BinderRunner runner) { synchronized (mBindLock) { @@ -267,7 +261,7 @@ public class ServiceWatcher implements ServiceConnection { } catch (Exception e) { // remote exceptions cannot be allowed to crash system server Log.e(TAG, "exception while while running " + runner + " on " + service - + " from " + mBestComponent.toShortString(), e); + + " from " + this, e); } } } @@ -280,14 +274,6 @@ public class ServiceWatcher implements ServiceConnection { UserHandle.USER_SYSTEM).isEmpty(); } - /** - * Searches and binds to the best package, or do nothing if the best package - * is already bound, unless force rebinding is requested. - * - * @param forceRebind Force a rebinding to the best package if it's already - * bound. - */ - @WorkerThread private void bindBestPackage(boolean forceRebind) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); @@ -365,7 +351,6 @@ public class ServiceWatcher implements ServiceConnection { } } - @WorkerThread private void bind(ComponentName component, int version, int userId) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); @@ -382,7 +367,6 @@ public class ServiceWatcher implements ServiceConnection { UserHandle.of(userId)); } - @WorkerThread private void unbind() { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); @@ -396,7 +380,6 @@ public class ServiceWatcher implements ServiceConnection { mBestUserId = UserHandle.USER_NULL; } - @MainThread @Override public final void onServiceConnected(ComponentName component, IBinder binder) { mHandler.post(() -> { @@ -410,7 +393,6 @@ public class ServiceWatcher implements ServiceConnection { }); } - @MainThread @Override public final void onServiceDisconnected(ComponentName component) { mHandler.post(() -> { diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index dbea529b72fe..4b092b299029 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -2526,6 +2526,19 @@ class StorageManagerService extends IStorageManager.Stub } } + /** + * Signal that checkpointing partitions should commit changes + */ + @Override + public void commitChanges() throws RemoteException { + // Only the system process is permitted to commit checkpoints + if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) { + throw new SecurityException("no permission to commit checkpoint changes"); + } + + mVold.commitChanges(); + } + @Override public String getPassword() throws RemoteException { mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index a2cbfaa02bfb..b04ae1746d18 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -67,7 +67,9 @@ import com.android.server.am.BatteryStatsService; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; /** @@ -196,6 +198,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private ArrayList<List<PhysicalChannelConfig>> mPhysicalChannelConfigs; + private Map<Integer, List<EmergencyNumber>> mEmergencyNumberList; + private int[] mSrvccState; private int mDefaultSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -229,8 +233,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { | PhoneStateListener.LISTEN_CELL_INFO; static final int ENFORCE_PHONE_STATE_PERMISSION_MASK = - PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR | - PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR; + PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR + | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR + | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST; static final int PRECISE_PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_PRECISE_CALL_STATE | @@ -357,6 +362,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellInfo = new ArrayList<List<CellInfo>>(); mSrvccState = new int[numPhones]; mPhysicalChannelConfigs = new ArrayList<List<PhysicalChannelConfig>>(); + mEmergencyNumberList = new HashMap<>(); for (int i = 0; i < numPhones; i++) { mCallState[i] = TelephonyManager.CALL_STATE_IDLE; mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE; @@ -752,6 +758,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if ((events & PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST) != 0) { + try { + r.callback.onEmergencyNumberListChanged(mEmergencyNumberList); + } catch (RemoteException ex) { + remove(r.binder); + } + } if ((events & PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE) != 0) { try { r.callback.onPhoneCapabilityChanged(mPhoneCapability); @@ -1665,10 +1678,30 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } @Override - public void notifyEmergencyNumberList(List<EmergencyNumber> emergencyNumberList) { - // TODO checkPermission, modify Listener constent documentation - // TODO implement multisim emergency number list update in listener - // TODO implement PhoneStateListenerTest + public void notifyEmergencyNumberList() { + if (!checkNotifyPermission("notifyEmergencyNumberList()")) { + return; + } + + synchronized (mRecords) { + mEmergencyNumberList = TelephonyManager.getDefault().getCurrentEmergencyNumberList(); + + for (Record r : mRecords) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST)) { + try { + r.callback.onEmergencyNumberListChanged(mEmergencyNumberList); + if (VDBG) { + log("notifyEmergencyNumberList: emergencyNumberList= " + + mEmergencyNumberList); + } + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + handleRemoveListLocked(); + } } @@ -1710,6 +1743,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mPhoneCapability=" + mPhoneCapability); pw.println("mPreferredDataSubId=" + mPreferredDataSubId); pw.println("mRadioPowerState=" + mRadioPowerState); + pw.println("mEmergencyNumberList=" + mEmergencyNumberList); pw.decreaseIndent(); diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index bbb1d13bdcdc..9f353a80a5be 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -16,7 +16,9 @@ package com.android.server; +import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.IUidObserver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -111,6 +113,7 @@ public class VibratorService extends IVibratorService.Stub private final boolean mSupportsAmplitudeControl; private final int mDefaultVibrationAmplitude; private final SparseArray<VibrationEffect> mFallbackEffects; + private final SparseArray<Integer> mProcStatesCache = new SparseArray(); private final WorkSource mTmpWorkSource = new WorkSource(); private final Handler mH = new Handler(); private final Object mLock = new Object(); @@ -147,6 +150,25 @@ public class VibratorService extends IVibratorService.Stub native static void vibratorSetAmplitude(int amplitude); native static long vibratorPerformEffect(long effect, long strength); + private final IUidObserver mUidObserver = new IUidObserver.Stub() { + @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) { + mProcStatesCache.put(uid, procState); + } + + @Override public void onUidGone(int uid, boolean disabled) { + mProcStatesCache.delete(uid); + } + + @Override public void onUidActive(int uid) { + } + + @Override public void onUidIdle(int uid, boolean disabled) { + } + + @Override public void onUidCachedChanged(int uid, boolean cached) { + } + }; + private class Vibration implements IBinder.DeathRecipient { public final IBinder token; // Start time in CLOCK_BOOTTIME base. @@ -411,6 +433,14 @@ public class VibratorService extends IVibratorService.Stub } }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH); + try { + ActivityManager.getService().registerUidObserver(mUidObserver, + ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE, + ActivityManager.PROCESS_STATE_UNKNOWN, null); + } catch (RemoteException e) { + // ignored; both services live in system_server + } + updateVibrators(); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); @@ -502,6 +532,12 @@ public class VibratorService extends IVibratorService.Stub return; } verifyIncomingUid(uid); + if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) + > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { + Slog.e(TAG, "Ignoring incoming vibration as process with uid = " + + uid + " is background"); + return; + } if (!verifyVibrationEffect(effect)) { return; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 23287cf399ca..f7acf7e83200 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1205,10 +1205,20 @@ public final class ActiveServices { android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE, r.app.pid, r.appInfo.uid, "startForeground"); } - } else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.P) { - mAm.enforcePermission( - android.Manifest.permission.FOREGROUND_SERVICE, - r.app.pid, r.appInfo.uid, "startForeground"); + } else { + if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.P) { + mAm.enforcePermission( + android.Manifest.permission.FOREGROUND_SERVICE, + r.app.pid, r.appInfo.uid, "startForeground"); + } + if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.Q) { + if (r.serviceInfo.getForegroundServiceType() + == ServiceInfo.FOREGROUND_SERVICE_TYPE_UNSPECIFIED) { + // STOPSHIP(b/120611119): replace log message with SecurityException. + Slog.w(TAG, "missing foregroundServiceType attribute in " + + "service element of manifest file"); + } + } } boolean alreadyStartedOp = false; boolean stopProcStatsOp = false; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c16f1db5c579..69cfcc21cb19 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1199,8 +1199,8 @@ public class ActivityManagerService extends IActivityManager.Stub void setProfileProc(ProcessRecord profileProc) { mProfileProc = profileProc; if (mAtmInternal != null) { - mAtmInternal.setProfileProc( - profileProc.getWindowProcessController()); + mAtmInternal.setProfileProc(profileProc == null ? null + : profileProc.getWindowProcessController()); } } @@ -2724,47 +2724,86 @@ public class ActivityManagerService extends IActivityManager.Stub return (ai.flags&ApplicationInfo.FLAG_PERSISTENT) != 0; } - void updateUsageStats(ComponentName activity, int uid, int userId, boolean resumed) { + /** + * Update battery stats on the activity' usage. + * @param activity + * @param uid + * @param userId + * @param resumed + */ + void updateBatteryStats(ComponentName activity, int uid, int userId, boolean resumed) { if (DEBUG_SWITCH) { Slog.d(TAG_SWITCH, - "updateUsageStats: comp=" + activity + "res=" + resumed); + "updateBatteryStats: comp=" + activity + "res=" + resumed); } final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); StatsLog.write(StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED, - uid, activity.getPackageName(), - activity.getShortClassName(), resumed ? - StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__FOREGROUND : + uid, activity.getPackageName(), activity.getShortClassName(), + resumed ? StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__FOREGROUND : StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED__STATE__BACKGROUND); - if (resumed) { - if (mUsageStatsService != null) { - mUsageStatsService.reportEvent(activity, userId, - UsageEvents.Event.MOVE_TO_FOREGROUND); - - } - synchronized (stats) { + synchronized (stats) { + if (resumed) { stats.noteActivityResumedLocked(uid); + } else { + stats.noteActivityPausedLocked(uid); } - } else { + } + } + + /** + * Update UsageStas on the activity's usage. + * @param activity + * @param userId + * @param event + * @param appToken ActivityRecord's appToken. + */ + public void updateActivityUsageStats(ComponentName activity, int userId, int event, + IBinder appToken) { + if (DEBUG_SWITCH) { + Slog.d(TAG_SWITCH, "updateActivityUsageStats: comp=" + + activity + " hash=" + appToken.hashCode() + " event=" + event); + } + synchronized (this) { if (mUsageStatsService != null) { - mUsageStatsService.reportEvent(activity, userId, - UsageEvents.Event.MOVE_TO_BACKGROUND); + mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode()); } - synchronized (stats) { - stats.noteActivityPausedLocked(uid); + } + } + + /** + * Update UsageStats on this package's usage. + * @param packageName + * @param userId + * @param event + */ + public void updateActivityUsageStats(String packageName, int userId, int event) { + if (DEBUG_SWITCH) { + Slog.d(TAG_SWITCH, "updateActivityUsageStats: package=" + + packageName + " event=" + event); + } + synchronized (this) { + if (mUsageStatsService != null) { + mUsageStatsService.reportEvent(packageName, userId, event); } } } + /** + * Update Usages on this foreground service's usage. + * @param service + * @param userId + * @param started + */ void updateForegroundServiceUsageStats(ComponentName service, int userId, boolean started) { if (DEBUG_SWITCH) { Slog.d(TAG_SWITCH, "updateForegroundServiceUsageStats: comp=" - + service + "started=" + started); + + service + " started=" + started); } synchronized (this) { if (mUsageStatsService != null) { mUsageStatsService.reportEvent(service, userId, started ? UsageEvents.Event.FOREGROUND_SERVICE_START - : UsageEvents.Event.FOREGROUND_SERVICE_STOP); + : UsageEvents.Event.FOREGROUND_SERVICE_STOP, 0); } } } @@ -7608,7 +7647,7 @@ public class ActivityManagerService extends IActivityManager.Stub String extraOptions = null; switch (bugreportType) { case ActivityManager.BUGREPORT_OPTION_FULL: - // Default options. + extraOptions = "bugreportfull"; break; case ActivityManager.BUGREPORT_OPTION_INTERACTIVE: extraOptions = "bugreportplus"; @@ -17532,8 +17571,9 @@ public class ActivityManagerService extends IActivityManager.Stub // how many slots we have for background processes; we may want // to put multiple processes in a slot of there are enough of // them. - int numSlots = (ProcessList.CACHED_APP_MAX_ADJ - - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2; + final int numSlots = (ProcessList.CACHED_APP_MAX_ADJ + - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2 + / ProcessList.CACHED_APP_IMPORTANCE_LEVELS; int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs; if (numEmptyProcs > cachedProcessLimit) { // If there are more empty processes than our limit on cached @@ -17544,17 +17584,19 @@ public class ActivityManagerService extends IActivityManager.Stub // instead of a gazillion empty processes. numEmptyProcs = cachedProcessLimit; } - int emptyFactor = numEmptyProcs/numSlots; + int emptyFactor = (numEmptyProcs + numSlots - 1) / numSlots; if (emptyFactor < 1) emptyFactor = 1; - int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1)/numSlots; + int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + numSlots - 1) : 1) + / numSlots; if (cachedFactor < 1) cachedFactor = 1; - int stepCached = 0; - int stepEmpty = 0; + int stepCached = -1; + int stepEmpty = -1; int numCached = 0; int numCachedExtraGroup = 0; int numEmpty = 0; int numTrimming = 0; int lastCachedGroup = 0; + int lastCachedGroupImportance = 0; int lastCachedGroupUid = 0; mNumNonCachedProcs = 0; @@ -17563,9 +17605,10 @@ public class ActivityManagerService extends IActivityManager.Stub // First update the OOM adjustment for each of the // application processes based on their current state. int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ; - int nextCachedAdj = curCachedAdj+1; - int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ; - int nextEmptyAdj = curEmptyAdj+2; + int nextCachedAdj = curCachedAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2); + int curCachedImpAdj = 0; + int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS; + int nextEmptyAdj = curEmptyAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2); boolean retryCycles = false; @@ -17590,53 +17633,74 @@ public class ActivityManagerService extends IActivityManager.Stub case PROCESS_STATE_CACHED_ACTIVITY: case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: case ActivityManager.PROCESS_STATE_CACHED_RECENT: - // This process is a cached process holding activities... - // assign it the next cached value for that type, and then - // step that cached level. - app.setCurRawAdj(curCachedAdj); - app.curAdj = app.modifyRawOomAdj(curCachedAdj); - if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i - + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj - + ")"); - if (curCachedAdj != nextCachedAdj) { + // Figure out the next cached level, taking into account groups. + boolean inGroup = false; + if (app.connectionGroup != 0) { + if (lastCachedGroupUid == app.uid + && lastCachedGroup == app.connectionGroup) { + // This is in the same group as the last process, just tweak + // adjustment by importance. + if (app.connectionImportance > lastCachedGroupImportance) { + lastCachedGroupImportance = app.connectionImportance; + if (curCachedAdj < nextCachedAdj + && curCachedAdj < ProcessList.CACHED_APP_MAX_ADJ) { + curCachedImpAdj++; + } + } + inGroup = true; + } else { + lastCachedGroupUid = app.uid; + lastCachedGroup = app.connectionGroup; + lastCachedGroupImportance = app.connectionImportance; + } + } + if (!inGroup && curCachedAdj != nextCachedAdj) { stepCached++; + curCachedImpAdj = 0; if (stepCached >= cachedFactor) { stepCached = 0; curCachedAdj = nextCachedAdj; - nextCachedAdj += 2; + nextCachedAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2; if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; } } } + // This process is a cached process holding activities... + // assign it the next cached value for that type, and then + // step that cached level. + app.setCurRawAdj(curCachedAdj + curCachedImpAdj); + app.curAdj = app.modifyRawOomAdj(curCachedAdj + curCachedImpAdj); + if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i + + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj + + " curCachedImpAdj=" + curCachedImpAdj + ")"); break; default: - // For everything else, assign next empty cached process - // level and bump that up. Note that this means that - // long-running services that have dropped down to the - // cached level will be treated as empty (since their process - // state is still as a service), which is what we want. - app.setCurRawAdj(curEmptyAdj); - app.curAdj = app.modifyRawOomAdj(curEmptyAdj); - if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning empty LRU #" + i - + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj - + ")"); + // Figure out the next cached level. if (curEmptyAdj != nextEmptyAdj) { stepEmpty++; if (stepEmpty >= emptyFactor) { stepEmpty = 0; curEmptyAdj = nextEmptyAdj; - nextEmptyAdj += 2; + nextEmptyAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2; if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) { nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ; } } } + // For everything else, assign next empty cached process + // level and bump that up. Note that this means that + // long-running services that have dropped down to the + // cached level will be treated as empty (since their process + // state is still as a service), which is what we want. + app.setCurRawAdj(curEmptyAdj); + app.curAdj = app.modifyRawOomAdj(curEmptyAdj); + if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning empty LRU #" + i + + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj + + ")"); break; } } - - } } @@ -17668,6 +17732,8 @@ public class ActivityManagerService extends IActivityManager.Stub } } + lastCachedGroup = lastCachedGroupUid = 0; + for (int i=N-1; i>=0; i--) { ProcessRecord app = mProcessList.mLruProcesses.get(i); if (!app.killedByAm && app.thread != null) { @@ -18935,7 +19001,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessMemoryState processMemoryState = new ProcessMemoryState(uid, r.processName, - r.maxAdj, + r.curAdj, memoryStat.pgfault, memoryStat.pgmajfault, memoryStat.rssInBytes, @@ -19041,9 +19107,19 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void updateUsageStats(ComponentName activity, int uid, int userId, boolean resumed) { + public void updateBatteryStats(ComponentName activity, int uid, int userId, + boolean resumed) { + synchronized (ActivityManagerService.this) { + ActivityManagerService.this.updateBatteryStats(activity, uid, userId, resumed); + } + } + + @Override + public void updateActivityUsageStats(ComponentName activity, int userId, int event, + IBinder appToken) { synchronized (ActivityManagerService.this) { - ActivityManagerService.this.updateUsageStats(activity, uid, userId, resumed); + ActivityManagerService.this.updateActivityUsageStats(activity, userId, event, + appToken); } } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 8c39d75ea6a4..3a0899de75c3 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -1337,7 +1337,7 @@ public final class BroadcastQueue { // Broadcast is being executed, its package can't be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( - r.curComponent.getPackageName(), false, UserHandle.getUserId(r.callingUid)); + r.curComponent.getPackageName(), false, r.userId); } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 9cfd39ced294..65cd329b5de8 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -55,7 +55,12 @@ final class CoreSettingsObserver extends ContentObserver { // add other system settings here... sGlobalSettingToTypeMap.put(Settings.Global.DEBUG_VIEW_ATTRIBUTES, int.class); - sGlobalSettingToTypeMap.put(Settings.Global.ANGLE_ENABLED_APP, String.class); + sGlobalSettingToTypeMap.put( + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE, String.class); + sGlobalSettingToTypeMap.put( + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, String.class); + sGlobalSettingToTypeMap.put( + Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, String.class); sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class); sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class); sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS, String.class); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 8cf9f1e9ae4a..117984e10cf5 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -137,9 +137,13 @@ public final class ProcessList { // This is a process only hosting activities that are not visible, // so it can be killed without any disruption. - static final int CACHED_APP_MAX_ADJ = 906; + static final int CACHED_APP_MAX_ADJ = 999; static final int CACHED_APP_MIN_ADJ = 900; + // Number of levels we have available for different service connection group importance + // levels. + static final int CACHED_APP_IMPORTANCE_LEVELS = 5; + // The B list of SERVICE_ADJ -- these are the old and decrepit // services that aren't as shiny and interesting as the ones in the A list. static final int SERVICE_B_ADJ = 800; diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 271b37ec7568..bcce05289206 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -67,6 +67,7 @@ import android.os.IRemoteCallback; import android.os.IUserManager; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -332,6 +333,14 @@ class UserController implements Handler.Callback { return; } } + // Inform checkpointing systems of success + try { + getStorageManager().commitChanges(); + } catch (Exception e) { + PowerManager pm = (PowerManager) + mInjector.getContext().getSystemService(Context.POWER_SERVICE); + pm.reboot("Checkpoint commit failed"); + } // We always walk through all the user lifecycle states to send // consistent developer events. We step into RUNNING_LOCKED here, diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index add55eaad166..1882be26ebf0 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -813,6 +813,24 @@ public class BiometricService extends SystemService { } } + @Override // Binder call + public void resetTimeout(byte[] token) { + checkInternalPermission(); + final long ident = Binder.clearCallingIdentity(); + try { + if (mFingerprintService != null) { + mFingerprintService.resetTimeout(token); + } + if (mFaceService != null) { + mFaceService.resetTimeout(token); + } + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + void cancelInternal(IBinder token, String opPackageName, boolean fromClient) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index 9649ccd3c750..32219aa5955f 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -282,9 +282,11 @@ public abstract class BiometricServiceBase extends SystemService public EnrollClientImpl(Context context, DaemonWrapper daemon, long halDeviceId, IBinder token, ServiceListener listener, int userId, int groupId, - byte[] cryptoToken, boolean restricted, String owner) { + byte[] cryptoToken, boolean restricted, String owner, + final int[] disabledFeatures) { super(context, getMetrics(), daemon, halDeviceId, token, listener, - userId, groupId, cryptoToken, restricted, owner, getBiometricUtils()); + userId, groupId, cryptoToken, restricted, owner, getBiometricUtils(), + disabledFeatures); } @Override @@ -408,7 +410,8 @@ public abstract class BiometricServiceBase extends SystemService int cancel() throws RemoteException; int remove(int groupId, int biometricId) throws RemoteException; int enumerate() throws RemoteException; - int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException; + int enroll(byte[] cryptoToken, int groupId, int timeout, + ArrayList<Integer> disabledFeatures) throws RemoteException; } /** diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java index f858ef5ec6f8..8a0f0858cedc 100644 --- a/services/core/java/com/android/server/biometrics/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/EnrollClient.java @@ -34,15 +34,18 @@ public abstract class EnrollClient extends ClientMonitor { private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute private final byte[] mCryptoToken; private final BiometricUtils mBiometricUtils; + private final int[] mDisabledFeatures; public EnrollClient(Context context, Metrics metrics, BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token, BiometricServiceBase.ServiceListener listener, int userId, int groupId, - byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils) { + byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils, + final int[] disabledFeatures) { super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted, owner, 0 /* cookie */); mBiometricUtils = utils; mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length); + mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length); } @Override @@ -74,7 +77,13 @@ public abstract class EnrollClient extends ClientMonitor { public int start() { final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); try { - final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), timeout); + final ArrayList<Integer> disabledFeatures = new ArrayList<>(); + for (int i = 0; i < mDisabledFeatures.length; i++) { + disabledFeatures.add(mDisabledFeatures[i]); + } + + final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), timeout, + disabledFeatures); if (result != 0) { Slog.w(getLogTag(), "startEnroll failed, result=" + result); mMetricsLogger.histogram(mMetrics.tagEnrollStartError(), result); diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 557af0478b87..72f73f6aaf67 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -113,17 +113,17 @@ public class FaceService extends BiometricServiceBase { } @Override // Binder call - public void enroll(final IBinder token, final byte[] cryptoToken, final int userId, - final IFaceServiceReceiver receiver, final int flags, - final String opPackageName) { + public void enroll(final IBinder token, final byte[] cryptoToken, + final IFaceServiceReceiver receiver, final String opPackageName, + final int[] disabledFeatures) { checkPermission(MANAGE_BIOMETRIC); final boolean restricted = isRestricted(); final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, - 0 /* groupId */, cryptoToken, restricted, opPackageName); + 0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures); - enrollInternal(client, userId); + enrollInternal(client, UserHandle.getCallingUserId()); } @Override // Binder call @@ -333,7 +333,7 @@ public class FaceService extends BiometricServiceBase { } @Override - public int setRequireAttention(boolean requireAttention, final byte[] token) { + public int setFeature(int feature, boolean enabled, final byte[] token) { checkPermission(MANAGE_BIOMETRIC); final ArrayList<Byte> byteToken = new ArrayList<>(); @@ -343,10 +343,11 @@ public class FaceService extends BiometricServiceBase { int result; try { - result = mDaemon != null ? mDaemon.setRequireAttention(requireAttention, byteToken) + result = mDaemon != null ? mDaemon.setFeature(feature, enabled, byteToken) : Status.INTERNAL_ERROR; } catch (RemoteException e) { - Slog.e(getTag(), "Unable to setRequireAttention to " + requireAttention, e); + Slog.e(getTag(), "Unable to set feature: " + feature + " to enabled:" + enabled, + e); result = Status.INTERNAL_ERROR; } @@ -354,17 +355,12 @@ public class FaceService extends BiometricServiceBase { } @Override - public boolean getRequireAttention(final byte[] token) { + public boolean getFeature(int feature) { checkPermission(MANAGE_BIOMETRIC); - final ArrayList<Byte> byteToken = new ArrayList<>(); - for (int i = 0; i < token.length; i++) { - byteToken.add(token[i]); - } - boolean result = true; try { - result = mDaemon != null ? mDaemon.getRequireAttention(byteToken).value : true; + result = mDaemon != null ? mDaemon.getFeature(feature) : true; } catch (RemoteException e) { Slog.e(getTag(), "Unable to getRequireAttention", e); } @@ -612,7 +608,8 @@ public class FaceService extends BiometricServiceBase { } @Override - public int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException { + public int enroll(byte[] cryptoToken, int groupId, int timeout, + ArrayList<Integer> disabledFeatures) throws RemoteException { IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { Slog.w(TAG, "enroll(): no face HAL!"); @@ -623,7 +620,7 @@ public class FaceService extends BiometricServiceBase { token.add(cryptoToken[i]); } // TODO: plumb requireAttention down from framework - return daemon.enroll(token, timeout, true /* requireAttention */); + return daemon.enroll(token, timeout, disabledFeatures); } }; diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index 6a5bc61f2eb6..3895ef78b357 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -144,7 +144,7 @@ public class FingerprintService extends BiometricServiceBase { final int groupId = userId; // default group for fingerprint enrollment final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId, - cryptoToken, restricted, opPackageName); + cryptoToken, restricted, opPackageName, new int[0] /* disabledFeatures */); enrollInternal(client, userId); } @@ -716,7 +716,8 @@ public class FingerprintService extends BiometricServiceBase { } @Override - public int enroll(byte[] cryptoToken, int groupId, int timeout) throws RemoteException { + public int enroll(byte[] cryptoToken, int groupId, int timeout, + ArrayList<Integer> disabledFeatures) throws RemoteException { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "enroll(): no fingerprint HAL!"); diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 65537adccbc6..258c325716f2 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -55,7 +55,7 @@ import android.util.SparseArray; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.intelligence.IntelligenceManagerInternal; +import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -158,7 +158,7 @@ public class ClipboardService extends SystemService { private final IUserManager mUm; private final PackageManager mPm; private final AppOpsManager mAppOps; - private final IntelligenceManagerInternal mIm; + private final ContentCaptureManagerInternal mContentCaptureInternal; private final IBinder mPermissionOwner; private HostClipboardMonitor mHostClipboardMonitor = null; private Thread mHostMonitorThread = null; @@ -178,7 +178,7 @@ public class ClipboardService extends SystemService { mPm = getContext().getPackageManager(); mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE); mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); - mIm = LocalServices.getService(IntelligenceManagerInternal.class); + mContentCaptureInternal = LocalServices.getService(ContentCaptureManagerInternal.class); final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard"); mPermissionOwner = permOwner; if (IS_EMULATOR) { @@ -652,9 +652,10 @@ public class ClipboardService extends SystemService { case AppOpsManager.OP_READ_CLIPBOARD: // Clipboard can only be read by applications with focus.. boolean allowed = mWm.isUidFocused(callingUid); - if (!allowed && mIm != null) { + if (!allowed && mContentCaptureInternal != null) { // ...or the Intelligence Service - allowed = mIm.isIntelligenceServiceForUser(callingUid, userId); + allowed = mContentCaptureInternal.isContentCaptureServiceForUser(callingUid, + userId); } if (!allowed) { Slog.e(TAG, "Denying clipboard access to " + callingPackage diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 3c14393ca740..d75601be23e3 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -944,10 +944,11 @@ public class Tethering extends BaseNetworkObserver { public boolean hasTetherableConfiguration() { final TetheringConfiguration cfg = mConfig; final boolean hasDownstreamConfiguration = - (cfg.tetherableUsbRegexs.length != 0) || - (cfg.tetherableWifiRegexs.length != 0) || - (cfg.tetherableBluetoothRegexs.length != 0); - final boolean hasUpstreamConfiguration = !cfg.preferredUpstreamIfaceTypes.isEmpty(); + (cfg.tetherableUsbRegexs.length != 0) + || (cfg.tetherableWifiRegexs.length != 0) + || (cfg.tetherableBluetoothRegexs.length != 0); + final boolean hasUpstreamConfiguration = !cfg.preferredUpstreamIfaceTypes.isEmpty() + || cfg.chooseUpstreamAutomatically; return hasDownstreamConfiguration && hasUpstreamConfiguration; } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index b7ed2f9bd473..602aedbc2d00 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -206,45 +206,6 @@ public class Vpn { // Handle of the user initiating VPN. private final int mUserHandle; - // Listen to package removal and change events (update/uninstall) for this user - private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final Uri data = intent.getData(); - final String packageName = data == null ? null : data.getSchemeSpecificPart(); - if (packageName == null) { - return; - } - - synchronized (Vpn.this) { - // Avoid race where always-on package has been unset - if (!packageName.equals(getAlwaysOnPackage())) { - return; - } - - final String action = intent.getAction(); - Log.i(TAG, "Received broadcast " + action + " for always-on VPN package " - + packageName + " in user " + mUserHandle); - - switch(action) { - case Intent.ACTION_PACKAGE_REPLACED: - // Start vpn after app upgrade - startAlwaysOnVpn(); - break; - case Intent.ACTION_PACKAGE_REMOVED: - final boolean isPackageRemoved = !intent.getBooleanExtra( - Intent.EXTRA_REPLACING, false); - if (isPackageRemoved) { - setAlwaysOnPackage(null, false); - } - break; - } - } - } - }; - - private boolean mIsPackageIntentReceiverRegistered = false; - public Vpn(Looper looper, Context context, INetworkManagementService netService, @UserIdInt int userHandle) { this(looper, context, netService, userHandle, new SystemServices(context)); @@ -500,7 +461,6 @@ public class Vpn { // Prepare this app. The notification will update as a side-effect of updateState(). prepareInternal(packageName); } - maybeRegisterPackageChangeReceiverLocked(packageName); setVpnForcedLocked(mLockdown); return true; } @@ -509,31 +469,6 @@ public class Vpn { return packageName == null || VpnConfig.LEGACY_VPN.equals(packageName); } - private void unregisterPackageChangeReceiverLocked() { - if (mIsPackageIntentReceiverRegistered) { - mContext.unregisterReceiver(mPackageIntentReceiver); - mIsPackageIntentReceiverRegistered = false; - } - } - - private void maybeRegisterPackageChangeReceiverLocked(String packageName) { - // Unregister IntentFilter listening for previous always-on package change - unregisterPackageChangeReceiverLocked(); - - if (!isNullOrLegacyVpn(packageName)) { - mIsPackageIntentReceiverRegistered = true; - - IntentFilter intentFilter = new IntentFilter(); - // Protected intent can only be sent by system. No permission required in register. - intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); - intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - intentFilter.addDataScheme("package"); - intentFilter.addDataSchemeSpecificPart(packageName, PatternMatcher.PATTERN_LITERAL); - mContext.registerReceiverAsUser( - mPackageIntentReceiver, UserHandle.of(mUserHandle), intentFilter, null, null); - } - } - /** * @return the package name of the VPN controller responsible for always-on VPN, * or {@code null} if none is set or always-on VPN is controlled through @@ -1302,7 +1237,6 @@ public class Vpn { setLockdown(false); mAlwaysOn = false; - unregisterPackageChangeReceiverLocked(); // Quit any active connections agentDisconnect(); } diff --git a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java new file mode 100644 index 000000000000..726362a7df36 --- /dev/null +++ b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 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.contentcapture; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.os.Bundle; +import android.os.IBinder; + +/** + * ContentCapture Manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class ContentCaptureManagerInternal { + + /** + * Checks whether the given {@code uid} owns the + * {@link android.service.contentcapture.ContentCaptureService} implementation associated with + * the given {@code userId}. + */ + public abstract boolean isContentCaptureServiceForUser(int uid, @UserIdInt int userId); + + /** + * Notifies the intelligence service of new assist data for the given activity. + * + * @return {@code false} if there was no service set for the given user + */ + public abstract boolean sendActivityAssistData(@UserIdInt int userId, + @NonNull IBinder activityToken, @NonNull Bundle data); +} diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 360a7d105cce..cf8d21b66417 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -46,6 +46,8 @@ import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener; import android.hardware.display.DisplayViewport; +import android.hardware.display.DisplayedContentSample; +import android.hardware.display.DisplayedContentSamplingAttributes; import android.hardware.display.IDisplayManager; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; @@ -1241,6 +1243,29 @@ public final class DisplayManagerService extends SystemService { } } + @VisibleForTesting + DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributesInternal( + int displayId) { + IBinder displayToken = SurfaceControl.getBuiltInDisplay(displayId); + return SurfaceControl.getDisplayedContentSamplingAttributes(displayToken); + } + + @VisibleForTesting + boolean setDisplayedContentSamplingEnabledInternal( + int displayId, boolean enable, int componentMask, int maxFrames) { + IBinder displayToken = SurfaceControl.getBuiltInDisplay(displayId); + return SurfaceControl.setDisplayedContentSamplingEnabled( + displayToken, enable, componentMask, maxFrames); + } + + @VisibleForTesting + DisplayedContentSample getDisplayedContentSampleInternal(int displayId, + long maxFrames, long timestamp) { + IBinder displayToken = SurfaceControl.getBuiltInDisplay(displayId); + return SurfaceControl.getDisplayedContentSample( + displayToken, maxFrames, timestamp); + } + private void clearViewportsLocked() { mViewports.clear(); } @@ -2331,5 +2356,25 @@ public final class DisplayManagerService extends SystemService { } } } + + @Override + public DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributes( + int displayId) { + return getDisplayedContentSamplingAttributesInternal(displayId); + } + + @Override + public boolean setDisplayedContentSamplingEnabled( + int displayId, boolean enable, int componentMask, int maxFrames) { + return setDisplayedContentSamplingEnabledInternal( + displayId, enable, componentMask, maxFrames); + } + + @Override + public DisplayedContentSample getDisplayedContentSample(int displayId, + long maxFrames, long timestamp) { + return getDisplayedContentSampleInternal(displayId, maxFrames, timestamp); + } + } } diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java index 5ca1755131ab..4ad26dae8380 100644 --- a/services/core/java/com/android/server/display/DisplayTransformManager.java +++ b/services/core/java/com/android/server/display/DisplayTransformManager.java @@ -23,7 +23,6 @@ import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; -import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -68,7 +67,7 @@ public class DisplayTransformManager { * SurfaceFlinger display color (managed, unmanaged, etc.). */ private static final int SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR = 1023; - private static final int SURFACE_FLINGER_TRANSACTION_QUERY_WIDE_COLOR = 1030; + private static final int SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED = 1030; private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation"; private static final String PERSISTENT_PROPERTY_DISPLAY_COLOR = "persist.sys.sf.native_mode"; @@ -270,8 +269,8 @@ public class DisplayTransformManager { } /** - * Returns whether the screen is wide color gamut via SurfaceFlinger's - * {@link #SURFACE_FLINGER_TRANSACTION_QUERY_WIDE_COLOR}. + * Returns whether the screen is color managed via SurfaceFlinger's + * {@link #SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED}. */ public boolean isDeviceColorManaged() { final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER); @@ -280,10 +279,10 @@ public class DisplayTransformManager { final Parcel reply = Parcel.obtain(); data.writeInterfaceToken("android.ui.ISurfaceComposer"); try { - flinger.transact(SURFACE_FLINGER_TRANSACTION_QUERY_WIDE_COLOR, data, reply, 0); + flinger.transact(SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED, data, reply, 0); return reply.readBoolean(); } catch (RemoteException ex) { - Log.e(TAG, "Failed to query wide color support", ex); + Slog.e(TAG, "Failed to query wide color support", ex); } finally { data.recycle(); reply.recycle(); @@ -305,7 +304,7 @@ public class DisplayTransformManager { try { flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0); } catch (RemoteException ex) { - Log.e(TAG, "Failed to set saturation", ex); + Slog.e(TAG, "Failed to set saturation", ex); } finally { data.recycle(); } @@ -325,7 +324,7 @@ public class DisplayTransformManager { try { flinger.transact(SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR, data, null, 0); } catch (RemoteException ex) { - Log.e(TAG, "Failed to set display color", ex); + Slog.e(TAG, "Failed to set display color", ex); } finally { data.recycle(); } @@ -336,7 +335,7 @@ public class DisplayTransformManager { try { ActivityTaskManager.getService().updateConfiguration(null); } catch (RemoteException e) { - Log.e(TAG, "Could not update configuration", e); + Slog.e(TAG, "Could not update configuration", e); } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index de0f29851da5..25ca27836aa7 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -1082,13 +1082,14 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); if (!canStartArcUpdateAction(message.getSource(), true)) { - if (getAvrDeviceInfo() == null) { + HdmiDeviceInfo avrDeviceInfo = getAvrDeviceInfo(); + if (avrDeviceInfo == null) { // AVR may not have been discovered yet. Delay the message processing. mDelayedMessageBuffer.add(message); return true; } mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - if (!isConnectedToArcPort(message.getSource())) { + if (!isConnectedToArcPort(avrDeviceInfo.getPhysicalAddress())) { displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); } return true; diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java index ef9ad8ae770c..c0c4a6e8310e 100644 --- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java +++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java @@ -58,7 +58,7 @@ import java.util.List; * <ul> * <li>Disabling the service when {@link UserManager} restrictions change. * <li>Refreshing the service when its underlying - * {@link #getSettingsProperty() Settings property} changed. + * {@link #getServiceSettingsProperty() Settings property} changed. * <li>Calling the service when other Settings properties changed. * </ul> * diff --git a/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java b/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java index 513a6a3eeec8..aaea45e4adbf 100644 --- a/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java +++ b/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java @@ -19,6 +19,7 @@ package com.android.server.infra; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; +import android.os.IInterface; import android.util.Slog; import java.io.PrintWriter; @@ -29,21 +30,21 @@ import java.util.ArrayList; * bound. * * @param <S> the concrete remote service class - * + * @param <I> the interface of the binder service * @hide */ -public abstract class AbstractMultiplePendingRequestsRemoteService< - S extends AbstractMultiplePendingRequestsRemoteService<S>> - extends AbstractRemoteService<S> { +public abstract class AbstractMultiplePendingRequestsRemoteService<S + extends AbstractMultiplePendingRequestsRemoteService<S, I>, I extends IInterface> + extends AbstractRemoteService<S, I> { private final int mInitialCapacity; - protected ArrayList<PendingRequest<S>> mPendingRequests; + protected ArrayList<PendingRequest<S, I>> mPendingRequests; public AbstractMultiplePendingRequestsRemoteService(@NonNull Context context, @NonNull String serviceInterface, @NonNull ComponentName componentName, int userId, - @NonNull VultureCallback callback, boolean bindInstantServiceAllowed, boolean verbose, - int initialCapacity) { + @NonNull VultureCallback<S> callback, boolean bindInstantServiceAllowed, + boolean verbose, int initialCapacity) { super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed, verbose); mInitialCapacity = initialCapacity; @@ -84,7 +85,7 @@ public abstract class AbstractMultiplePendingRequestsRemoteService< } @Override // from AbstractRemoteService - void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S> pendingRequest) { + void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S, I> pendingRequest) { if (mPendingRequests == null) { mPendingRequests = new ArrayList<>(mInitialCapacity); } diff --git a/services/core/java/com/android/server/infra/AbstractRemoteService.java b/services/core/java/com/android/server/infra/AbstractRemoteService.java index 7af1d4ca94ef..41dcf89a0b04 100644 --- a/services/core/java/com/android/server/infra/AbstractRemoteService.java +++ b/services/core/java/com/android/server/infra/AbstractRemoteService.java @@ -54,13 +54,13 @@ import java.lang.ref.WeakReference; * (no pun intended) example of how to use it. * * @param <S> the concrete remote service class + * @param <I> the interface of the binder service * * @hide */ //TODO(b/117779333): improve javadoc above instead of using Autofill as an example -public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> - implements DeathRecipient { - +public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I>, + I extends IInterface> implements DeathRecipient { private static final int MSG_UNBIND = 1; protected static final int LAST_PRIVATE_MSG = MSG_UNBIND; @@ -74,11 +74,11 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> private final Context mContext; private final Intent mIntent; - private final VultureCallback mVultureCallback; + private final VultureCallback<S> mVultureCallback; private final int mUserId; private final ServiceConnection mServiceConnection = new RemoteServiceConnection(); private final boolean mBindInstantServiceAllowed; - private IInterface mServiceInterface; + protected I mService; private boolean mBinding; private boolean mDestroyed; @@ -87,19 +87,21 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> /** * Callback called when the service dies. + * + * @param <T> service class */ - public interface VultureCallback { + public interface VultureCallback<T> { /** * Called when the service dies. * * @param service service that died! */ - void onServiceDied(AbstractRemoteService<? extends AbstractRemoteService<?>> service); + void onServiceDied(T service); } // NOTE: must be package-protected so this class is not extend outside AbstractRemoteService(@NonNull Context context, @NonNull String serviceInterface, - @NonNull ComponentName componentName, int userId, @NonNull VultureCallback callback, + @NonNull ComponentName componentName, int userId, @NonNull VultureCallback<S> callback, boolean bindInstantServiceAllowed, boolean verbose) { mContext = context; mVultureCallback = callback; @@ -150,7 +152,7 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> * Gets the base Binder interface from the service. */ @NonNull - protected abstract IInterface getServiceInterface(@NonNull IBinder service); + protected abstract I getServiceInterface(@NonNull IBinder service); /** * Defines How long after the last interaction with the service we would unbind. @@ -183,12 +185,14 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> private void handleBinderDied() { if (checkIfDestroyed()) return; - if (mServiceInterface != null) { - mServiceInterface.asBinder().unlinkToDeath(this, 0); + if (mService != null) { + mService.asBinder().unlinkToDeath(this, 0); } - mServiceInterface = null; + mService = null; mServiceDied = true; - mVultureCallback.onServiceDied(this); + @SuppressWarnings("unchecked") // TODO(b/117779333): fix this warning + final S castService = (S) this; + mVultureCallback.onServiceDied(castService); } // Note: we are dumping without a lock held so this is a bit racy but @@ -216,12 +220,35 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> pw.println(); } - protected void scheduleRequest(@NonNull PendingRequest<S> pendingRequest) { + /** + * Schedules a "sync" request. + * + * <p>This request must be responded by the service somehow (typically using a callback), + * othewise it will trigger a {@link PendingRequest#onTimeout(AbstractRemoteService)} if the + * service doesn't respond. + */ + protected void scheduleRequest(@NonNull PendingRequest<S, I> pendingRequest) { + cancelScheduledUnbind(); mHandler.sendMessage(obtainMessage( AbstractRemoteService::handlePendingRequest, this, pendingRequest)); } - protected void cancelScheduledUnbind() { + /** + * Schedules an async request. + * + * <p>This request is not expecting a callback from the service, hence it's represented by + * a simple {@link Runnable}. + */ + protected void scheduleAsyncRequest(@NonNull AsyncRequest<I> request) { + cancelScheduledUnbind(); + // TODO(b/117779333): fix generics below + @SuppressWarnings({"unchecked", "rawtypes"}) + final MyAsyncPendingRequest<S, I> asyncRequest = new MyAsyncPendingRequest(this, request); + mHandler.sendMessage( + obtainMessage(AbstractRemoteService::handlePendingRequest, this, asyncRequest)); + } + + private void cancelScheduledUnbind() { mHandler.removeMessages(MSG_UNBIND); } @@ -244,11 +271,11 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> * Handles a request, either processing it right now when bound, or saving it to be handled when * bound. */ - protected final void handlePendingRequest(@NonNull PendingRequest<S> pendingRequest) { + protected final void handlePendingRequest(@NonNull PendingRequest<S, I> pendingRequest) { if (checkIfDestroyed() || mCompleted) return; if (!handleIsBound()) { - if (mVerbose) Slog.v(mTag, "handlePendingRequest(): queuing" + pendingRequest); + if (mVerbose) Slog.v(mTag, "handlePendingRequest(): queuing " + pendingRequest); handlePendingRequestWhileUnBound(pendingRequest); handleEnsureBound(); } else { @@ -263,10 +290,10 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> /** * Defines what to do with a request that arrives while not bound to the service. */ - abstract void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S> pendingRequest); + abstract void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S, I> pendingRequest); private boolean handleIsBound() { - return mServiceInterface != null; + return mService != null; } private void handleEnsureBound() { @@ -300,9 +327,9 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> mBinding = false; if (handleIsBound()) { handleOnConnectedStateChangedInternal(false); - if (mServiceInterface != null) { - mServiceInterface.asBinder().unlinkToDeath(this, 0); - mServiceInterface = null; + if (mService != null) { + mService.asBinder().unlinkToDeath(this, 0); + mService = null; } } mContext.unbindService(mServiceConnection); @@ -318,7 +345,7 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> return; } mBinding = false; - mServiceInterface = getServiceInterface(service); + mService = getServiceInterface(service); try { service.linkToDeath(AbstractRemoteService.this, 0); } catch (RemoteException re) { @@ -332,7 +359,7 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> @Override public void onServiceDisconnected(ComponentName name) { mBinding = true; - mServiceInterface = null; + mService = null; } } @@ -349,10 +376,15 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> /** * Base class for the requests serviced by the remote service. * + * <p><b>NOTE: </b> this class is typically used when the service needs to use a callback to + * communicate back with the system server. For cases where that's not needed, you should use + * {@link AbstractRemoteService#scheduleAsyncRequest(AsyncRequest)} instead. + * * @param <S> the remote service class + * @param <I> the interface of the binder service */ - public abstract static class PendingRequest<S extends AbstractRemoteService<S>> - implements Runnable { + public abstract static class PendingRequest<S extends AbstractRemoteService<S, I>, + I extends IInterface> implements Runnable { protected final String mTag = getClass().getSimpleName(); protected final Object mLock = new Object(); @@ -366,7 +398,7 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> @GuardedBy("mLock") private boolean mCompleted; - protected PendingRequest(S service) { + protected PendingRequest(@NonNull S service) { mWeakService = new WeakReference<>(service); mServiceHandler = service.mHandler; mTimeoutTrigger = () -> { @@ -379,6 +411,7 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> final S remoteService = mWeakService.get(); if (remoteService != null) { + // TODO(b/117779333): we should probably ignore it if service is destroyed. Slog.w(mTag, "timed out after " + service.getRemoteRequestMillis() + " ms"); onTimeout(remoteService); } else { @@ -451,4 +484,50 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S>> return false; } } + + /** + * Represents a request that does not expect a callback from the remote service. + * + * @param <I> the interface of the binder service + */ + public interface AsyncRequest<I extends IInterface> { + + /** + * Run Forrest, run! + */ + void run(@NonNull I binder) throws RemoteException; + } + + private static final class MyAsyncPendingRequest<S extends AbstractRemoteService<S, I>, + I extends IInterface> extends PendingRequest<S, I> { + private static final String TAG = MyAsyncPendingRequest.class.getSimpleName(); + + private final AsyncRequest<I> mRequest; + + protected MyAsyncPendingRequest(@NonNull S service, @NonNull AsyncRequest<I> request) { + super(service); + + mRequest = request; + } + + @Override + public void run() { + final S remoteService = getService(); + if (remoteService == null) return; + try { + mRequest.run(remoteService.mService); + } catch (RemoteException e) { + Slog.w(TAG, "exception handling async request (" + this + "): " + e); + } finally { + finish(); + } + } + + @Override + protected void onTimeout(S remoteService) { + // TODO(b/117779333): should not happen because we called finish() on run(), although + // currently it might be called if the service is destroyed while showing it. + Slog.w(TAG, "AsyncPending requested timed out"); + } + } } diff --git a/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java b/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java index 8f8b448a7a66..d32f13b5d71b 100644 --- a/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java +++ b/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java @@ -19,6 +19,7 @@ package com.android.server.infra; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; +import android.os.IInterface; import android.util.Slog; import java.io.PrintWriter; @@ -29,17 +30,19 @@ import java.io.PrintWriter; * <p>If another request is received while not bound, the previous one will be canceled. * * @param <S> the concrete remote service class + * @param <I> the interface of the binder service * * @hide */ -public abstract class AbstractSinglePendingRequestRemoteService< - S extends AbstractSinglePendingRequestRemoteService<S>> extends AbstractRemoteService<S> { +public abstract class AbstractSinglePendingRequestRemoteService<S + extends AbstractSinglePendingRequestRemoteService<S, I>, I extends IInterface> + extends AbstractRemoteService<S, I> { - protected PendingRequest<S> mPendingRequest; + protected PendingRequest<S, I> mPendingRequest; public AbstractSinglePendingRequestRemoteService(@NonNull Context context, @NonNull String serviceInterface, @NonNull ComponentName componentName, int userId, - @NonNull VultureCallback callback, boolean bindInstantServiceAllowed, + @NonNull VultureCallback<S> callback, boolean bindInstantServiceAllowed, boolean verbose) { super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed, verbose); @@ -48,7 +51,7 @@ public abstract class AbstractSinglePendingRequestRemoteService< @Override // from AbstractRemoteService void handlePendingRequests() { if (mPendingRequest != null) { - final PendingRequest<S> pendingRequest = mPendingRequest; + final PendingRequest<S, I> pendingRequest = mPendingRequest; mPendingRequest = null; handlePendingRequest(pendingRequest); } @@ -70,10 +73,11 @@ public abstract class AbstractSinglePendingRequestRemoteService< } @Override // from AbstractRemoteService - void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S> pendingRequest) { + void handlePendingRequestWhileUnBound(@NonNull PendingRequest<S, I> pendingRequest) { if (mPendingRequest != null) { if (mVerbose) { - Slog.v(mTag, "handlePendingRequestWhileUnBound(): cancelling " + mPendingRequest); + Slog.v(mTag, "handlePendingRequestWhileUnBound(): cancelling " + mPendingRequest + + " to handle " + pendingRequest); } mPendingRequest.cancel(); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index d96b6cba119b..e7c3c7bbe21b 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1951,6 +1951,11 @@ public class InputManagerService extends IInputManager.Stub } // Native callback. + private int getPointerDisplayId() { + return mWindowManagerCallbacks.getPointerDisplayId(); + } + + // Native callback. private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) { if (!mSystemReady) { return null; @@ -2017,6 +2022,8 @@ public class InputManagerService extends IInputManager.Stub KeyEvent event, int policyFlags); public int getPointerLayer(); + + public int getPointerDisplayId(); } /** diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index a8da9680c4f5..28a6ba4ceb1d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -984,9 +984,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // {@link #canShowInputMethodPickerLocked(IInputMethodClient)}. mHandler.obtainMessage( MSG_SHOW_IM_SUBTYPE_PICKER, + // TODO(b/120076400): Design and implement IME switcher for heterogeneous + // navbar configuration. InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES, - 0 /* arg2 */) - .sendToTarget(); + DEFAULT_DISPLAY).sendToTarget(); } else { Slog.w(TAG, "Unexpected intent " + intent); } @@ -1617,7 +1618,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Check whether or not this is a valid IPC. Assumes an IPC is valid when either // 1) it comes from the system process // 2) the calling process' user id is identical to the current user id IMMS thinks. - private boolean calledFromValidUser() { + @GuardedBy("mMethodMap") + private boolean calledFromValidUserLocked() { final int uid = Binder.getCallingUid(); final int userId = UserHandle.getUserId(uid); if (DEBUG) { @@ -1657,7 +1659,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * @param token The window token given to the input method when it was started. * @return true if and only if non-null valid token is specified. */ - private boolean calledWithValidToken(@Nullable IBinder token) { + @GuardedBy("mMethodMap") + private boolean calledWithValidTokenLocked(@Nullable IBinder token) { if (token == null && Binder.getCallingPid() == Process.myPid()) { if (DEBUG) { // TODO(b/34851776): Basically it's the caller's fault if we reach here. @@ -1698,11 +1701,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private List<InputMethodInfo> getInputMethodList(final boolean isVrOnly) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUser()) { - return Collections.emptyList(); - } synchronized (mMethodMap) { + // TODO: Make this work even for non-current users? + if (!calledFromValidUserLocked()) { + return Collections.emptyList(); + } ArrayList<InputMethodInfo> methodList = new ArrayList<>(); for (InputMethodInfo info : mMethodList) { @@ -1716,11 +1719,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public List<InputMethodInfo> getEnabledInputMethodList() { - // TODO: Make this work even for non-current users? - if (!calledFromValidUser()) { - return Collections.emptyList(); - } synchronized (mMethodMap) { + // TODO: Make this work even for non-current users? + if (!calledFromValidUserLocked()) { + return Collections.emptyList(); + } return mSettings.getEnabledInputMethodListLocked(); } } @@ -1732,11 +1735,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, boolean allowsImplicitlySelectedSubtypes) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUser()) { - return Collections.emptyList(); - } synchronized (mMethodMap) { + // TODO: Make this work even for non-current users? + if (!calledFromValidUserLocked()) { + return Collections.emptyList(); + } final InputMethodInfo imi; if (imiId == null && mCurMethodId != null) { imi = mMethodMap.get(mCurMethodId); @@ -2238,7 +2241,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private void updateStatusIcon(@NonNull IBinder token, String packageName, @DrawableRes int iconId) { synchronized (mMethodMap) { - if (!calledWithValidToken(token)) { + if (!calledWithValidTokenLocked(token)) { return; } final long ident = Binder.clearCallingIdentity(); @@ -2341,11 +2344,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @SuppressWarnings("deprecation") private void setImeWindowStatus(IBinder token, int vis, int backDisposition) { - if (!calledWithValidToken(token)) { - return; - } - synchronized (mMethodMap) { + if (!calledWithValidTokenLocked(token)) { + return; + } mImeWindowVis = vis; mBackDisposition = backDisposition; updateSystemUiLocked(token, vis, backDisposition); @@ -2376,11 +2378,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void reportStartInput(IBinder token, IBinder startInputToken) { - if (!calledWithValidToken(token)) { - return; - } - synchronized (mMethodMap) { + if (!calledWithValidTokenLocked(token)) { + return; + } final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken); if (targetWindow != null && mLastImeTargetWindow != targetWindow) { mWindowManagerInternal.updateInputMethodTargetWindow(token, targetWindow); @@ -2391,7 +2392,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Caution! This method is called in this class. Handle multi-user carefully private void updateSystemUiLocked(IBinder token, int vis, int backDisposition) { - if (!calledWithValidToken(token)) { + if (!calledWithValidTokenLocked(token)) { return; } @@ -2450,10 +2451,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) { - if (!calledFromValidUser()) { - return; - } synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return; + } final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); for (int i = 0; i < spans.length; ++i) { SuggestionSpan ss = spans[i]; @@ -2466,10 +2467,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) { - if (!calledFromValidUser()) { - return false; - } synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return false; + } final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span); // TODO: Do not send the intent if the process of the targetImi is already dead. if (targetImi != null) { @@ -2633,13 +2634,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean showSoftInput(IInputMethodClient client, int flags, ResultReceiver resultReceiver) { - if (!calledFromValidUser()) { - return false; - } int uid = Binder.getCallingUid(); - long ident = Binder.clearCallingIdentity(); - try { - synchronized (mMethodMap) { + synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return false; + } + final long ident = Binder.clearCallingIdentity(); + try { if (mCurClient == null || client == null || mCurClient.client.asBinder() != client.asBinder()) { // We need to check if this is the current client with @@ -2657,9 +2658,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); return showCurrentInputLocked(flags, resultReceiver); + } finally { + Binder.restoreCallingIdentity(ident); } - } finally { - Binder.restoreCallingIdentity(ident); } } @@ -2718,13 +2719,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean hideSoftInput(IInputMethodClient client, int flags, ResultReceiver resultReceiver) { - if (!calledFromValidUser()) { - return false; - } int uid = Binder.getCallingUid(); - long ident = Binder.clearCallingIdentity(); - try { - synchronized (mMethodMap) { + synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return false; + } + final long ident = Binder.clearCallingIdentity(); + try { if (mCurClient == null || client == null || mCurClient.client.asBinder() != client.asBinder()) { // We need to check if this is the current client with @@ -2745,9 +2746,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) Slog.v(TAG, "Client requesting input be hidden"); return hideCurrentInputLocked(flags, resultReceiver); + } finally { + Binder.restoreCallingIdentity(ident); } - } finally { - Binder.restoreCallingIdentity(ident); } } @@ -2827,14 +2828,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute, IInputContext inputContext, @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion) { - // Needs to check the validity before clearing calling identity - final boolean calledFromValidUser = calledFromValidUser(); InputBindResult res = null; - long ident = Binder.clearCallingIdentity(); - try { + synchronized (mMethodMap) { + // Needs to check the validity before clearing calling identity + final boolean calledFromValidUser = calledFromValidUserLocked(); final int windowDisplayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); - synchronized (mMethodMap) { + final long ident = Binder.clearCallingIdentity(); + try { if (DEBUG) Slog.v(TAG, "startInputOrWindowGainedFocusInternal: reason=" + InputMethodDebug.startInputReasonToString(startInputReason) + " client=" + client.asBinder() @@ -3023,9 +3024,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub res = InputBindResult.NULL_EDITOR_INFO; } } + } finally { + Binder.restoreCallingIdentity(ident); } - } finally { - Binder.restoreCallingIdentity(ident); } return res; @@ -3034,9 +3035,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private boolean canShowInputMethodPickerLocked(IInputMethodClient client) { // TODO(yukawa): multi-display support. final int uid = Binder.getCallingUid(); - if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) { - return true; - } else if (mCurFocusedWindowClient != null && client != null + if (mCurFocusedWindowClient != null && client != null && mCurFocusedWindowClient.client.asBinder() == client.asBinder()) { return true; } else if (mCurIntent != null && InputMethodUtils.checkIfPackageBelongsToUid( @@ -3044,22 +3043,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub uid, mCurIntent.getComponent().getPackageName())) { return true; - } else if (mContext.checkCallingPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS) - == PackageManager.PERMISSION_GRANTED) { - return true; } - return false; } @Override public void showInputMethodPickerFromClient( IInputMethodClient client, int auxiliarySubtypeMode) { - if (!calledFromValidUser()) { - return; - } synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return; + } if(!canShowInputMethodPickerLocked(client)) { Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid " + Binder.getCallingUid() + ": " + client); @@ -3068,11 +3062,26 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Always call subtype picker, because subtype picker is a superset of input method // picker. - mHandler.sendMessage(mCaller.obtainMessageI( - MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode)); + mHandler.sendMessage(mCaller.obtainMessageII( + MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, + (mCurClient != null) ? mCurClient.selfReportedDisplayId : DEFAULT_DISPLAY)); } } + @Override + public void showInputMethodPickerFromSystem(IInputMethodClient client, int auxiliarySubtypeMode, + int displayId) { + if (mContext.checkCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "showInputMethodPickerFromSystem requires WRITE_SECURE_SETTINGS permission"); + } + // Always call subtype picker, because subtype picker is a superset of input method + // picker. + mHandler.sendMessage(mCaller.obtainMessageII( + MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)); + } + public boolean isInputMethodPickerShownForTest() { synchronized(mMethodMap) { if (mSwitchingDialog == null) { @@ -3084,18 +3093,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void setInputMethod(IBinder token, String id) { - if (!calledFromValidUser()) { - return; + synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return; + } + setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID); } - setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID); } @Override public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { - if (!calledFromValidUser()) { - return; - } synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return; + } if (subtype != null) { setInputMethodWithSubtypeIdLocked(token, id, InputMethodUtils.getSubtypeIdFromHashCode(mMethodMap.get(id), @@ -3109,22 +3120,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void showInputMethodAndSubtypeEnablerFromClient( IInputMethodClient client, String inputMethodId) { - // TODO(yukawa): Should we verify the display ID? - if (!calledFromValidUser()) { - return; - } synchronized (mMethodMap) { + // TODO(yukawa): Should we verify the display ID? + if (!calledFromValidUserLocked()) { + return; + } executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); } } - @Override - public boolean switchToPreviousInputMethod(IBinder token) { - if (!calledFromValidUser()) { - return false; - } + @BinderThread + private boolean switchToPreviousInputMethod(IBinder token) { synchronized (mMethodMap) { + if (!calledWithValidTokenLocked(token)) { + return false; + } final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); final InputMethodInfo lastImi; if (lastIme != null) { @@ -3191,13 +3202,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @Override - public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) { - if (!calledFromValidUser()) { - return false; - } + @BinderThread + private boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) { synchronized (mMethodMap) { - if (!calledWithValidToken(token)) { + if (!calledWithValidTokenLocked(token)) { return false; } final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( @@ -3213,11 +3221,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token) { - if (!calledFromValidUser()) { - return false; - } synchronized (mMethodMap) { - if (!calledWithValidToken(token)) { + if (!calledWithValidTokenLocked(token)) { return false; } final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( @@ -3231,10 +3236,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public InputMethodSubtype getLastInputMethodSubtype() { - if (!calledFromValidUser()) { - return null; - } synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return null; + } final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); // TODO: Handle the case of the last IME with no subtypes if (lastIme == null || TextUtils.isEmpty(lastIme.first) @@ -3257,13 +3262,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) { - if (!calledFromValidUser()) { - return; - } // By this IPC call, only a process which shares the same uid with the IME can add // additional input method subtypes to the IME. if (TextUtils.isEmpty(imiId) || subtypes == null) return; synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return; + } if (!mSystemReady) { return; } @@ -3329,12 +3334,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) { - synchronized (mMethodMap) { - setInputMethodWithSubtypeIdLocked(token, id, subtypeId); - } - } - private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) { if (token == null) { if (mContext.checkCallingOrSelfPermission( @@ -3360,11 +3359,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void hideMySoftInput(@NonNull IBinder token, int flags) { - if (!calledFromValidUser()) { - return; - } synchronized (mMethodMap) { - if (!calledWithValidToken(token)) { + if (!calledWithValidTokenLocked(token)) { return; } long ident = Binder.clearCallingIdentity(); @@ -3378,11 +3374,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void showMySoftInput(@NonNull IBinder token, int flags) { - if (!calledFromValidUser()) { - return; - } synchronized (mMethodMap) { - if (!calledWithValidToken(token)) { + if (!calledWithValidTokenLocked(token)) { return; } long ident = Binder.clearCallingIdentity(); @@ -3421,6 +3414,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub switch (msg.what) { case MSG_SHOW_IM_SUBTYPE_PICKER: final boolean showAuxSubtypes; + final int displayId = msg.arg2; switch (msg.arg1) { case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO: // This is undocumented so far, but IMM#showInputMethodPicker() has been @@ -3438,7 +3432,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1); return false; } - showInputMethodMenu(showAuxSubtypes); + showInputMethodMenu(showAuxSubtypes, displayId); return true; case MSG_SHOW_IM_SUBTYPE_ENABLER: @@ -3818,7 +3812,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure(); } - private void showInputMethodMenu(boolean showAuxSubtypes) { + private void showInputMethodMenu(boolean showAuxSubtypes, int displayId) { if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes); final boolean isScreenLocked = isScreenLocked(); @@ -3864,8 +3858,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + final ActivityThread currentThread = ActivityThread.currentActivityThread(); final Context settingsContext = new ContextThemeWrapper( - ActivityThread.currentActivityThread().getSystemUiContext(), + displayId == DEFAULT_DISPLAY ? currentThread.getSystemUiContext() + : currentThread.createSystemUiContext(displayId), com.android.internal.R.style.Theme_DeviceDefault_Settings); mDialogBuilder = new AlertDialog.Builder(settingsContext); @@ -4195,11 +4191,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ @Override public InputMethodSubtype getCurrentInputMethodSubtype() { - // TODO: Make this work even for non-current users? - if (!calledFromValidUser()) { - return null; - } synchronized (mMethodMap) { + // TODO: Make this work even for non-current users? + if (!calledFromValidUserLocked()) { + return null; + } return getCurrentInputMethodSubtypeLocked(); } } @@ -4274,11 +4270,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUser()) { - return false; - } synchronized (mMethodMap) { + // TODO: Make this work even for non-current users? + if (!calledFromValidUserLocked()) { + return false; + } if (subtype != null && mCurMethodId != null) { InputMethodInfo imi = mMethodMap.get(mCurMethodId); int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()); @@ -4532,10 +4528,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private IInputContentUriToken createInputContentUriToken(@Nullable IBinder token, @Nullable Uri contentUri, @Nullable String packageName) { - if (!calledFromValidUser()) { - return null; - } - if (token == null) { throw new NullPointerException("token"); } @@ -4589,11 +4581,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void reportFullscreenMode(IBinder token, boolean fullscreen) { - if (!calledFromValidUser()) { - return; - } synchronized (mMethodMap) { - if (!calledWithValidToken(token)) { + if (!calledWithValidTokenLocked(token)) { return; } if (mCurClient != null && mCurClient.client != null) { @@ -4933,11 +4922,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) private int handleShellCommandEnableDisableInputMethod( @NonNull ShellCommand shellCommand, boolean enabled) { - if (!calledFromValidUser()) { - shellCommand.getErrPrintWriter().print( - "Must be called from the foreground user or with INTERACT_ACROSS_USERS_FULL"); - return ShellCommandResult.FAILURE; - } final String id = shellCommand.getNextArgRequired(); final boolean previouslyEnabled; @@ -4994,11 +4978,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @ShellCommandResult @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) private int handleShellCommandResetInputMethod(@NonNull ShellCommand shellCommand) { - if (!calledFromValidUser()) { - shellCommand.getErrPrintWriter().print( - "Must be called from the foreground user or with INTERACT_ACROSS_USERS_FULL"); - return ShellCommandResult.FAILURE; - } synchronized (mMethodMap) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.WRITE_SECURE_SETTINGS) diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 2b67fe72c2e7..5edb5c8e3286 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1541,6 +1541,13 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override + public void showInputMethodPickerFromSystem( + IInputMethodClient client, int auxiliarySubtypeMode, int displayId) { + reportNotSupported(); + } + + @BinderThread + @Override public void showInputMethodAndSubtypeEnablerFromClient( IInputMethodClient client, String inputMethodId) { reportNotSupported(); @@ -1595,20 +1602,6 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override - public boolean switchToPreviousInputMethod(IBinder token) { - reportNotSupported(); - return false; - } - - @BinderThread - @Override - public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) { - reportNotSupported(); - return false; - } - - @BinderThread - @Override public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) { reportNotSupported(); } diff --git a/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java b/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java deleted file mode 100644 index f424869a428e..000000000000 --- a/services/core/java/com/android/server/intelligence/IntelligenceManagerInternal.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2018 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.intelligence; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.os.Bundle; -import android.os.IBinder; -import android.view.autofill.AutofillId; -import android.view.autofill.AutofillValue; -import android.view.autofill.IAutoFillManagerClient; - -/** - * Intelligence Manager local system service interface. - * - * @hide Only for use within the system server. - */ -//TODO(b/111276913): rename once the final name is defined -public abstract class IntelligenceManagerInternal { - - /** - * Checks whether the given {@code uid} owns the - * {@link android.service.intelligence.SmartSuggestionsService} implementation associated with - * the given {@code userId}. - */ - public abstract boolean isIntelligenceServiceForUser(int uid, @UserIdInt int userId); - - /** - * Notifies the intelligence service of new assist data for the given activity. - * - * @return {@code false} if there was no service set for the given user - */ - public abstract boolean sendActivityAssistData(@UserIdInt int userId, - @NonNull IBinder activityToken, @NonNull Bundle data); - - /** - * Asks the intelligence service to provide Augmented Autofill for a given activity. - * - * @param userId user handle - * @param client binder used to communicate with the activity that originated this request. - * @param activityToken activity that originated this request. - * @param autofillSessionId autofill session id (must be used on {@code client} calls. - * @param focusedId id of the the field that triggered this request. - * @param focusedValue current value of the field that triggered this request. - * - * @return {@code false} if the service cannot handle this request, {@code true} otherwise. - * <b>NOTE: </b> it must return right away; typically it will return {@code false} if the - * service is disabled (or the activity blacklisted). - */ - public abstract AugmentedAutofillCallback requestAutofill(@UserIdInt int userId, - @NonNull IAutoFillManagerClient client, @NonNull IBinder activityToken, - int autofillSessionId, @NonNull AutofillId focusedId, - @Nullable AutofillValue focusedValue); - - /** - * Callback used by the Autofill Session to communicate with the Augmented Autofill service. - */ - public interface AugmentedAutofillCallback { - // TODO(b/111330312): this method is calling when the Autofill session is destroyed, the - // main reason being the cases where user tap HOME. - // Right now it's completely destroying the UI, but we need to decide whether / how to - // properly recover it later (for example, if the user switches back to the activity, - // should it be restored? Right not it kind of is, because Autofill's Session trigger a - // new FillRequest, which in turn triggers the Augmented Autofill request again) - /** - * Destroys the Autofill UI. - */ - void destroy(); - } -} diff --git a/services/core/java/com/android/server/job/JobConcurrencyManager.java b/services/core/java/com/android/server/job/JobConcurrencyManager.java new file mode 100644 index 000000000000..ee1d8476c471 --- /dev/null +++ b/services/core/java/com/android/server/job/JobConcurrencyManager.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2018 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.job; + +import android.app.ActivityManager; +import android.app.job.JobInfo; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.app.procstats.ProcessStats; +import com.android.server.job.controllers.JobStatus; +import com.android.server.job.controllers.StateController; + +import java.util.Iterator; +import java.util.List; + +class JobConcurrencyManager { + private static final String TAG = JobSchedulerService.TAG; + private static final boolean DEBUG = JobSchedulerService.DEBUG; + + private final Object mLock; + private final JobSchedulerService mService; + private final JobSchedulerService.Constants mConstants; + + private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; + + /** + * This array essentially stores the state of mActiveServices array. + * The ith index stores the job present on the ith JobServiceContext. + * We manipulate this array until we arrive at what jobs should be running on + * what JobServiceContext. + */ + JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT]; + + /** + * Indicates whether we need to act on this jobContext id + */ + boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT]; + + /** + * The uid whose jobs we would like to assign to a context. + */ + int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; + + JobConcurrencyManager(JobSchedulerService service) { + mService = service; + mLock = mService.mLock; + mConstants = service.mConstants; + } + + /** + * Takes jobs from pending queue and runs them on available contexts. + * If no contexts are available, preempts lower priority jobs to + * run higher priority ones. + * Lock on mJobs before calling this function. + */ + void assignJobsToContextsLocked() { + if (DEBUG) { + Slog.d(TAG, printPendingQueueLocked()); + } + + final JobPackageTracker tracker = mService.mJobPackageTracker; + final List<JobStatus> pendingJobs = mService.mPendingJobs; + final List<JobServiceContext> activeServices = mService.mActiveServices; + final List<StateController> controllers = mService.mControllers; + + int memLevel; + try { + memLevel = ActivityManager.getService().getMemoryTrimLevel(); + } catch (RemoteException e) { + memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL; + } + switch (memLevel) { + case ProcessStats.ADJ_MEM_FACTOR_MODERATE: + mService.mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT; + break; + case ProcessStats.ADJ_MEM_FACTOR_LOW: + mService.mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT; + break; + case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: + mService.mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT; + break; + default: + mService.mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT; + break; + } + + JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap; + boolean[] act = mTmpAssignAct; + int[] preferredUidForContext = mTmpAssignPreferredUidForContext; + int numActive = 0; + int numForeground = 0; + for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) { + final JobServiceContext js = mService.mActiveServices.get(i); + final JobStatus status = js.getRunningJobLocked(); + if ((contextIdToJobMap[i] = status) != null) { + numActive++; + if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { + numForeground++; + } + } + act[i] = false; + preferredUidForContext[i] = js.getPreferredUid(); + } + if (DEBUG) { + Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial")); + } + for (int i=0; i<pendingJobs.size(); i++) { + JobStatus nextPending = pendingJobs.get(i); + + // If job is already running, go to next job. + int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap); + if (jobRunningContext != -1) { + continue; + } + + final int priority = mService.evaluateJobPriorityLocked(nextPending); + nextPending.lastEvaluatedPriority = priority; + + // Find a context for nextPending. The context should be available OR + // it should have lowest priority among all running jobs + // (sharing the same Uid as nextPending) + int minPriority = Integer.MAX_VALUE; + int minPriorityContextId = -1; + for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) { + JobStatus job = contextIdToJobMap[j]; + int preferredUid = preferredUidForContext[j]; + if (job == null) { + if ((numActive < mService.mMaxActiveJobs || + (priority >= JobInfo.PRIORITY_TOP_APP && + numForeground < mConstants.FG_JOB_COUNT)) && + (preferredUid == nextPending.getUid() || + preferredUid == JobServiceContext.NO_PREFERRED_UID)) { + // This slot is free, and we haven't yet hit the limit on + // concurrent jobs... we can just throw the job in to here. + minPriorityContextId = j; + break; + } + // No job on this context, but nextPending can't run here because + // the context has a preferred Uid or we have reached the limit on + // concurrent jobs. + continue; + } + if (job.getUid() != nextPending.getUid()) { + continue; + } + if (mService.evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) { + continue; + } + if (minPriority > nextPending.lastEvaluatedPriority) { + minPriority = nextPending.lastEvaluatedPriority; + minPriorityContextId = j; + } + } + if (minPriorityContextId != -1) { + contextIdToJobMap[minPriorityContextId] = nextPending; + act[minPriorityContextId] = true; + numActive++; + if (priority >= JobInfo.PRIORITY_TOP_APP) { + numForeground++; + } + } + } + if (DEBUG) { + Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); + } + tracker.noteConcurrency(numActive, numForeground); + for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) { + boolean preservePreferredUid = false; + if (act[i]) { + JobStatus js = activeServices.get(i).getRunningJobLocked(); + if (js != null) { + if (DEBUG) { + Slog.d(TAG, "preempting job: " + + activeServices.get(i).getRunningJobLocked()); + } + // preferredUid will be set to uid of currently running job. + activeServices.get(i).preemptExecutingJobLocked(); + preservePreferredUid = true; + } else { + final JobStatus pendingJob = contextIdToJobMap[i]; + if (DEBUG) { + Slog.d(TAG, "About to run job on context " + + String.valueOf(i) + ", job: " + pendingJob); + } + for (int ic=0; ic<controllers.size(); ic++) { + controllers.get(ic).prepareForExecutionLocked(pendingJob); + } + if (!activeServices.get(i).executeRunnableJob(pendingJob)) { + Slog.d(TAG, "Error executing " + pendingJob); + } + if (pendingJobs.remove(pendingJob)) { + tracker.noteNonpending(pendingJob); + } + } + } + if (!preservePreferredUid) { + activeServices.get(i).clearPreferredUid(); + } + } + } + + int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) { + for (int i=0; i<map.length; i++) { + if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) { + return i; + } + } + return -1; + } + + private String printPendingQueueLocked() { + StringBuilder s = new StringBuilder("Pending queue: "); + Iterator<JobStatus> it = mService.mPendingJobs.iterator(); + while (it.hasNext()) { + JobStatus js = it.next(); + s.append("(") + .append(js.getJob().getId()) + .append(", ") + .append(js.getUid()) + .append(") "); + } + return s.toString(); + } + + private String printContextIdToJobMap(JobStatus[] map, String initial) { + StringBuilder s = new StringBuilder(initial + ": "); + for (int i=0; i<map.length; i++) { + s.append("(") + .append(map[i] == null? -1: map[i].getJobId()) + .append(map[i] == null? -1: map[i].getUid()) + .append(")" ); + } + return s.toString(); + } + +} diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index ea295de5909f..611c8b728d0c 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -54,6 +54,8 @@ import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Binder; import android.os.Handler; +import android.os.IThermalService; +import android.os.IThermalStatusListener; import android.os.Looper; import android.os.Message; import android.os.Process; @@ -62,6 +64,7 @@ import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.SystemClock; +import android.os.Temperature; import android.os.UserHandle; import android.os.UserManagerInternal; import android.provider.Settings; @@ -75,9 +78,9 @@ import android.util.StatsLog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; -import com.android.internal.app.procstats.ProcessStats; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; @@ -111,7 +114,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; @@ -135,7 +137,7 @@ public class JobSchedulerService extends com.android.server.SystemService public static final boolean DEBUG_STANDBY = DEBUG || false; /** The maximum number of concurrent jobs we run at one time. */ - private static final int MAX_JOB_CONTEXTS_COUNT = 16; + static final int MAX_JOB_CONTEXTS_COUNT = 16; /** Enforce a per-app limit on scheduled jobs? */ private static final boolean ENFORCE_MAX_JOBS = true; /** The maximum number of jobs that we allow an unprivileged app to schedule */ @@ -156,6 +158,7 @@ public class JobSchedulerService extends com.android.server.SystemService final StandbyTracker mStandbyTracker; /** Tracking amount of time each package runs for. */ final JobPackageTracker mJobPackageTracker = new JobPackageTracker(); + final JobConcurrencyManager mConcurrencyManager; static final int MSG_JOB_EXPIRED = 0; static final int MSG_CHECK_JOB = 1; @@ -173,13 +176,18 @@ public class JobSchedulerService extends com.android.server.SystemService final List<JobServiceContext> mActiveServices = new ArrayList<>(); /** List of controllers that will notify this service of updates to jobs. */ - private final List<StateController> mControllers; + final List<StateController> mControllers; /** Need direct access to this for testing. */ private final BatteryController mBatteryController; /** Need direct access to this for testing. */ private final StorageController mStorageController; /** Need directly for sending uid state changes */ private final DeviceIdleJobsController mDeviceIdleJobsController; + /** Need directly for receiving thermal events */ + private IThermalService mThermalService; + /** Thermal constraint. */ + @GuardedBy("mLock") + private boolean mThermalConstraint = false; /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list @@ -268,22 +276,6 @@ public class JobSchedulerService extends com.android.server.SystemService // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked -- - /** - * This array essentially stores the state of mActiveServices array. - * The ith index stores the job present on the ith JobServiceContext. - * We manipulate this array until we arrive at what jobs should be running on - * what JobServiceContext. - */ - JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT]; - /** - * Indicates whether we need to act on this jobContext id - */ - boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT]; - /** - * The uid whose jobs we would like to assign to a context. - */ - int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; - private class ConstantsObserver extends ContentObserver { private ContentResolver mResolver; @@ -327,6 +319,19 @@ public class JobSchedulerService extends com.android.server.SystemService } /** + * Thermal event received from Thermal Service + */ + private final class ThermalStatusListener extends IThermalStatusListener.Stub { + @Override public void onStatusChange(int status) { + // Throttle for Temperature.THROTTLING_SEVERE and above + synchronized (mLock) { + mThermalConstraint = status >= Temperature.THROTTLING_SEVERE; + } + onControllerStateChanged(); + } + } + + /** * All times are in milliseconds. These constants are kept synchronized with the system * global Settings. Any access to this class or its fields should be done while * holding the JobSchedulerService.mLock lock. @@ -1176,8 +1181,10 @@ public class JobSchedulerService extends com.android.server.SystemService // with just the foreground priority. This means that persistent processes // can never be the top app priority... that is fine. mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP); + } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { + mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_SERVICE); } else if (procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { - mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_APP); + mUidPriorityOverride.put(uid, JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE); } else { mUidPriorityOverride.delete(uid); } @@ -1267,6 +1274,8 @@ public class JobSchedulerService extends com.android.server.SystemService mConstantsObserver = new ConstantsObserver(mHandler); mJobSchedulerStub = new JobSchedulerStub(); + mConcurrencyManager = new JobConcurrencyManager(this); + // Set up the app standby bucketing tracker mStandbyTracker = new StandbyTracker(); mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); @@ -1381,6 +1390,16 @@ public class JobSchedulerService extends com.android.server.SystemService } // Remove any jobs that are not associated with any of the current users. cancelJobsForNonExistentUsers(); + // Register thermal callback + mThermalService = IThermalService.Stub.asInterface( + ServiceManager.getService(Context.THERMAL_SERVICE)); + if (mThermalService != null) { + try { + mThermalService.registerThermalStatusListener(new ThermalStatusListener()); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register thermal callback.", e); + } + } } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { synchronized (mLock) { // Let's go! @@ -1804,14 +1823,26 @@ public class JobSchedulerService extends com.android.server.SystemService } } + private boolean isJobThermalConstrainedLocked(JobStatus job) { + return mThermalConstraint && job.hasConnectivityConstraint() + && (evaluateJobPriorityLocked(job) < JobInfo.PRIORITY_FOREGROUND_APP); + } + private void stopNonReadyActiveJobsLocked() { for (int i=0; i<mActiveServices.size(); i++) { JobServiceContext serviceContext = mActiveServices.get(i); final JobStatus running = serviceContext.getRunningJobLocked(); - if (running != null && !running.isReady()) { + if (running == null) { + continue; + } + if (!running.isReady()) { serviceContext.cancelExecutingJobLocked( JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED, "cancelled due to unsatisfied constraints"); + } else if (isJobThermalConstrainedLocked(running)) { + serviceContext.cancelExecutingJobLocked( + JobParameters.REASON_DEVICE_THERMAL, + "cancelled due to thermal condition"); } } } @@ -2099,6 +2130,10 @@ public class JobSchedulerService extends com.android.server.SystemService return false; } + if (isJobThermalConstrainedLocked(job)) { + return false; + } + final boolean jobPending = mPendingJobs.contains(job); final boolean jobActive = isCurrentlyActiveLocked(job); @@ -2193,7 +2228,7 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs."); } - assignJobsToContextsLocked(); + mConcurrencyManager.assignJobsToContextsLocked(); reportActiveLocked(); } @@ -2209,9 +2244,9 @@ public class JobSchedulerService extends com.android.server.SystemService return curPriority; } - private int evaluateJobPriorityLocked(JobStatus job) { + int evaluateJobPriorityLocked(JobStatus job) { int priority = job.getPriority(); - if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) { + if (priority >= JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE) { return adjustJobPriority(priority, job); } int override = mUidPriorityOverride.get(job.getSourceUid(), 0); @@ -2221,161 +2256,6 @@ public class JobSchedulerService extends com.android.server.SystemService return adjustJobPriority(priority, job); } - /** - * Takes jobs from pending queue and runs them on available contexts. - * If no contexts are available, preempts lower priority jobs to - * run higher priority ones. - * Lock on mJobs before calling this function. - */ - private void assignJobsToContextsLocked() { - if (DEBUG) { - Slog.d(TAG, printPendingQueue()); - } - - int memLevel; - try { - memLevel = ActivityManager.getService().getMemoryTrimLevel(); - } catch (RemoteException e) { - memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL; - } - switch (memLevel) { - case ProcessStats.ADJ_MEM_FACTOR_MODERATE: - mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT; - break; - case ProcessStats.ADJ_MEM_FACTOR_LOW: - mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT; - break; - case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: - mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT; - break; - default: - mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT; - break; - } - - JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap; - boolean[] act = mTmpAssignAct; - int[] preferredUidForContext = mTmpAssignPreferredUidForContext; - int numActive = 0; - int numForeground = 0; - for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) { - final JobServiceContext js = mActiveServices.get(i); - final JobStatus status = js.getRunningJobLocked(); - if ((contextIdToJobMap[i] = status) != null) { - numActive++; - if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { - numForeground++; - } - } - act[i] = false; - preferredUidForContext[i] = js.getPreferredUid(); - } - if (DEBUG) { - Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial")); - } - for (int i=0; i<mPendingJobs.size(); i++) { - JobStatus nextPending = mPendingJobs.get(i); - - // If job is already running, go to next job. - int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap); - if (jobRunningContext != -1) { - continue; - } - - final int priority = evaluateJobPriorityLocked(nextPending); - nextPending.lastEvaluatedPriority = priority; - - // Find a context for nextPending. The context should be available OR - // it should have lowest priority among all running jobs - // (sharing the same Uid as nextPending) - int minPriority = Integer.MAX_VALUE; - int minPriorityContextId = -1; - for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) { - JobStatus job = contextIdToJobMap[j]; - int preferredUid = preferredUidForContext[j]; - if (job == null) { - if ((numActive < mMaxActiveJobs || - (priority >= JobInfo.PRIORITY_TOP_APP && - numForeground < mConstants.FG_JOB_COUNT)) && - (preferredUid == nextPending.getUid() || - preferredUid == JobServiceContext.NO_PREFERRED_UID)) { - // This slot is free, and we haven't yet hit the limit on - // concurrent jobs... we can just throw the job in to here. - minPriorityContextId = j; - break; - } - // No job on this context, but nextPending can't run here because - // the context has a preferred Uid or we have reached the limit on - // concurrent jobs. - continue; - } - if (job.getUid() != nextPending.getUid()) { - continue; - } - if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) { - continue; - } - if (minPriority > nextPending.lastEvaluatedPriority) { - minPriority = nextPending.lastEvaluatedPriority; - minPriorityContextId = j; - } - } - if (minPriorityContextId != -1) { - contextIdToJobMap[minPriorityContextId] = nextPending; - act[minPriorityContextId] = true; - numActive++; - if (priority >= JobInfo.PRIORITY_TOP_APP) { - numForeground++; - } - } - } - if (DEBUG) { - Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); - } - mJobPackageTracker.noteConcurrency(numActive, numForeground); - for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) { - boolean preservePreferredUid = false; - if (act[i]) { - JobStatus js = mActiveServices.get(i).getRunningJobLocked(); - if (js != null) { - if (DEBUG) { - Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJobLocked()); - } - // preferredUid will be set to uid of currently running job. - mActiveServices.get(i).preemptExecutingJobLocked(); - preservePreferredUid = true; - } else { - final JobStatus pendingJob = contextIdToJobMap[i]; - if (DEBUG) { - Slog.d(TAG, "About to run job on context " - + String.valueOf(i) + ", job: " + pendingJob); - } - for (int ic=0; ic<mControllers.size(); ic++) { - mControllers.get(ic).prepareForExecutionLocked(pendingJob); - } - if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) { - Slog.d(TAG, "Error executing " + pendingJob); - } - if (mPendingJobs.remove(pendingJob)) { - mJobPackageTracker.noteNonpending(pendingJob); - } - } - } - if (!preservePreferredUid) { - mActiveServices.get(i).clearPreferredUid(); - } - } - } - - int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) { - for (int i=0; i<map.length; i++) { - if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) { - return i; - } - } - return -1; - } - final class LocalService implements JobSchedulerInternal { /** @@ -3150,31 +3030,6 @@ public class JobSchedulerService extends com.android.server.SystemService getContext().sendBroadcastAsUser(dockIntent, UserHandle.ALL); } - private String printContextIdToJobMap(JobStatus[] map, String initial) { - StringBuilder s = new StringBuilder(initial + ": "); - for (int i=0; i<map.length; i++) { - s.append("(") - .append(map[i] == null? -1: map[i].getJobId()) - .append(map[i] == null? -1: map[i].getUid()) - .append(")" ); - } - return s.toString(); - } - - private String printPendingQueue() { - StringBuilder s = new StringBuilder("Pending queue: "); - Iterator<JobStatus> it = mPendingJobs.iterator(); - while (it.hasNext()) { - JobStatus js = it.next(); - s.append("(") - .append(js.getJob().getId()) - .append(", ") - .append(js.getUid()) - .append(") "); - } - return s.toString(); - } - static void dumpHelp(PrintWriter pw) { pw.println("Job Scheduler (jobscheduler) dump options:"); pw.println(" [-h] [package] ..."); @@ -3228,6 +3083,9 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(" In parole?: "); pw.print(mInParole); pw.println(); + pw.print(" In thermal throttling?: "); + pw.print(mThermalConstraint); + pw.println(); pw.println(); pw.println("Started users: " + Arrays.toString(mStartedUsers)); @@ -3325,9 +3183,9 @@ public class JobSchedulerService extends com.android.server.SystemService pw.println(job.toShortString()); job.dump(pw, " ", false, nowElapsed); int priority = evaluateJobPriorityLocked(job); - if (priority != JobInfo.PRIORITY_DEFAULT) { - pw.print(" Evaluated priority: "); pw.println(priority); - } + pw.print(" Evaluated priority: "); + pw.println(JobInfo.getPriorityString(priority)); + pw.print(" Tag: "); pw.println(job.getTag()); pw.print(" Enq: "); TimeUtils.formatDuration(job.madePending - nowUptime, pw); @@ -3358,9 +3216,9 @@ public class JobSchedulerService extends com.android.server.SystemService pw.println(); job.dump(pw, " ", false, nowElapsed); int priority = evaluateJobPriorityLocked(jsc.getRunningJobLocked()); - if (priority != JobInfo.PRIORITY_DEFAULT) { - pw.print(" Evaluated priority: "); pw.println(priority); - } + pw.print(" Evaluated priority: "); + pw.println(JobInfo.getPriorityString(priority)); + pw.print(" Active at "); TimeUtils.formatDuration(job.madeActive - nowUptime, pw); pw.print(", pending for "); @@ -3403,6 +3261,7 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT_TIME_MILLIS, mLastHeartbeatTime + mConstants.STANDBY_HEARTBEAT_TIME - nowUptime); proto.write(JobSchedulerServiceDumpProto.IN_PAROLE, mInParole); + proto.write(JobSchedulerServiceDumpProto.IN_THERMAL, mThermalConstraint); for (int u : mStartedUsers) { proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u); @@ -3478,10 +3337,7 @@ public class JobSchedulerService extends com.android.server.SystemService job.writeToShortProto(proto, PendingJob.INFO); job.dump(proto, PendingJob.DUMP, false, nowElapsed); - int priority = evaluateJobPriorityLocked(job); - if (priority != JobInfo.PRIORITY_DEFAULT) { - proto.write(PendingJob.EVALUATED_PRIORITY, priority); - } + proto.write(PendingJob.EVALUATED_PRIORITY, evaluateJobPriorityLocked(job)); proto.write(PendingJob.ENQUEUED_DURATION_MS, nowUptime - job.madePending); proto.end(pjToken); @@ -3513,10 +3369,8 @@ public class JobSchedulerService extends com.android.server.SystemService job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed); - int priority = evaluateJobPriorityLocked(jsc.getRunningJobLocked()); - if (priority != JobInfo.PRIORITY_DEFAULT) { - proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY, priority); - } + proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY, + evaluateJobPriorityLocked(jsc.getRunningJobLocked())); proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS, nowUptime - job.madeActive); diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index 6deecbd9a83b..434158957c17 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -1321,7 +1321,8 @@ public final class JobStatus { pw.print(prefix); pw.println(" PERSISTED"); } if (job.getPriority() != 0) { - pw.print(prefix); pw.print(" Priority: "); pw.println(job.getPriority()); + pw.print(prefix); pw.print(" Priority: "); + pw.println(JobInfo.getPriorityString(job.getPriority())); } if (job.getFlags() != 0) { pw.print(prefix); pw.print(" Flags: "); diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java new file mode 100644 index 000000000000..4c7c420214bd --- /dev/null +++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2010 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; + +import android.location.Location; +import android.location.LocationProvider; +import android.os.Bundle; +import android.os.WorkSource; + +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + +/** + * Location Manager's interface for location providers. + * + * @hide + */ +public abstract class AbstractLocationProvider { + + /** + * Interface for communicating from a location provider back to the location service. + */ + public interface LocationProviderManager { + + /** + * Called on location provider construction to make the location service aware of this + * provider and what it's initial enabled/disabled state should be. + */ + void onAttachProvider(AbstractLocationProvider locationProvider, boolean initiallyEnabled); + + /** + * May be called to inform the location service of a change in this location provider's + * enabled/disabled state. + */ + void onSetEnabled(boolean enabled); + + /** + * May be called to inform the location service of a change in this location provider's + * properties. + */ + void onSetProperties(ProviderProperties properties); + + /** + * May be called to inform the location service that this provider has a new location + * available. + */ + void onReportLocation(Location location); + + /** + * May be called to inform the location service that this provider has a new location + * available. + */ + void onReportLocation(List<Location> locations); + } + + private final LocationProviderManager mLocationProviderManager; + + protected AbstractLocationProvider(LocationProviderManager locationProviderManager) { + this(locationProviderManager, true); + } + + protected AbstractLocationProvider(LocationProviderManager locationProviderManager, + boolean initiallyEnabled) { + mLocationProviderManager = locationProviderManager; + mLocationProviderManager.onAttachProvider(this, initiallyEnabled); + } + + /** + * Call this method to report a new location. May be called from any thread. + */ + protected void reportLocation(Location location) { + mLocationProviderManager.onReportLocation(location); + } + + /** + * Call this method to report a new location. May be called from any thread. + */ + protected void reportLocation(List<Location> locations) { + mLocationProviderManager.onReportLocation(locations); + } + + /** + * Call this method to report a change in provider enabled/disabled status. May be called from + * any thread. + */ + protected void setEnabled(boolean enabled) { + mLocationProviderManager.onSetEnabled(enabled); + } + + /** + * Call this method to report a change in provider properties. May be called from + * any thread. + */ + protected void setProperties(ProviderProperties properties) { + mLocationProviderManager.onSetProperties(properties); + } + + /** + * Called when the location service delivers a new request for fulfillment to the provider. + * Replaces any previous requests completely. + */ + public abstract void setRequest(ProviderRequest request, WorkSource source); + + /** + * Called to dump debug or log information. + */ + public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args); + + /** + * Retrieves the current status of the provider. + * + * @deprecated Will be removed in a future release. + */ + @Deprecated + public int getStatus(Bundle extras) { + return LocationProvider.AVAILABLE; + } + + /** + * Retrieves the last update time of the status of the provider. + * + * @deprecated Will be removed in a future release. + */ + @Deprecated + public long getStatusUpdateTime() { + return 0; + } + + /** Sends a custom command to this provider. */ + public abstract void sendExtraCommand(String command, Bundle extras); +} diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java index 6612d0264aff..675e59e8f960 100644 --- a/services/core/java/com/android/server/location/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java @@ -237,7 +237,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub Supplier<Intent> supplier = () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, message.getNanoAppId()) .putExtra(ContextHubManager.EXTRA_MESSAGE, message); - sendPendingIntent(supplier); + sendPendingIntent(supplier, message.getNanoAppId()); } /** @@ -247,7 +247,8 @@ public class ContextHubClientBroker extends IContextHubClient.Stub */ /* package */ void onNanoAppLoaded(long nanoAppId) { invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId)); - sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId)); + sendPendingIntent( + () -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId), nanoAppId); } /** @@ -257,7 +258,8 @@ public class ContextHubClientBroker extends IContextHubClient.Stub */ /* package */ void onNanoAppUnloaded(long nanoAppId) { invokeCallback(callback -> callback.onNanoAppUnloaded(nanoAppId)); - sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_NANOAPP_UNLOADED, nanoAppId)); + sendPendingIntent( + () -> createIntent(ContextHubManager.EVENT_NANOAPP_UNLOADED, nanoAppId), nanoAppId); } /** @@ -280,7 +282,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub Supplier<Intent> supplier = () -> createIntent(ContextHubManager.EVENT_NANOAPP_ABORTED, nanoAppId) .putExtra(ContextHubManager.EXTRA_NANOAPP_ABORT_CODE, abortCode); - sendPendingIntent(supplier); + sendPendingIntent(supplier, nanoAppId); } /** @@ -360,18 +362,40 @@ public class ContextHubClientBroker extends IContextHubClient.Stub */ private synchronized void sendPendingIntent(Supplier<Intent> supplier) { if (mPendingIntentRequest.hasPendingIntent()) { - Intent intent = supplier.get(); - try { - mPendingIntentRequest.getPendingIntent().send( - mContext, 0 /* code */, intent, null /* onFinished */, null /* Handler */, - Manifest.permission.LOCATION_HARDWARE /* requiredPermission */, - null /* options */); - } catch (PendingIntent.CanceledException e) { - // The PendingIntent is no longer valid - Log.w(TAG, "PendingIntent has been canceled, unregistering from client" - + " (host endpoint ID " + mHostEndPointId + ")"); - close(); - } + doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get()); + } + } + + /** + * Sends an intent to any existing PendingIntent + * + * @param supplier method to create the extra Intent + * @param nanoAppId the ID of the nanoapp which this event is for + */ + private synchronized void sendPendingIntent(Supplier<Intent> supplier, long nanoAppId) { + if (mPendingIntentRequest.hasPendingIntent() + && mPendingIntentRequest.getNanoAppId() == nanoAppId) { + doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get()); + } + } + + /** + * Sends a PendingIntent with extra Intent data + * + * @param pendingIntent the PendingIntent + * @param intent the extra Intent data + */ + private void doSendPendingIntent(PendingIntent pendingIntent, Intent intent) { + try { + pendingIntent.send( + mContext, 0 /* code */, intent, null /* onFinished */, null /* Handler */, + Manifest.permission.LOCATION_HARDWARE /* requiredPermission */, + null /* options */); + } catch (PendingIntent.CanceledException e) { + // The PendingIntent is no longer valid + Log.w(TAG, "PendingIntent has been canceled, unregistering from client" + + " (host endpoint ID " + mHostEndPointId + ")"); + close(); } } diff --git a/services/core/java/com/android/server/location/GnssGeofenceProvider.java b/services/core/java/com/android/server/location/GnssGeofenceProvider.java index 6ac4aeb7f9ea..a84b0b1c4335 100644 --- a/services/core/java/com/android/server/location/GnssGeofenceProvider.java +++ b/services/core/java/com/android/server/location/GnssGeofenceProvider.java @@ -1,18 +1,12 @@ package com.android.server.location; import android.location.IGpsGeofenceHardware; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; import android.util.Log; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; - /** * Manages GNSS Geofence operations. */ @@ -34,26 +28,26 @@ class GnssGeofenceProvider extends IGpsGeofenceHardware.Stub { public boolean paused; } + private final Object mLock = new Object(); + @GuardedBy("mLock") private final GnssGeofenceProviderNative mNative; + @GuardedBy("mLock") private final SparseArray<GeofenceEntry> mGeofenceEntries = new SparseArray<>(); - private final Handler mHandler; - GnssGeofenceProvider(Looper looper) { - this(looper, new GnssGeofenceProviderNative()); + GnssGeofenceProvider() { + this(new GnssGeofenceProviderNative()); } @VisibleForTesting - GnssGeofenceProvider(Looper looper, GnssGeofenceProviderNative gnssGeofenceProviderNative) { - mHandler = new Handler(looper); + GnssGeofenceProvider(GnssGeofenceProviderNative gnssGeofenceProviderNative) { mNative = gnssGeofenceProviderNative; } - // TODO(b/37460011): use this method in HAL death recovery. void resumeIfStarted() { if (DEBUG) { Log.d(TAG, "resumeIfStarted"); } - mHandler.post(() -> { + synchronized (mLock) { for (int i = 0; i < mGeofenceEntries.size(); i++) { GeofenceEntry entry = mGeofenceEntries.valueAt(i); boolean added = mNative.addGeofence(entry.geofenceId, entry.latitude, @@ -65,30 +59,21 @@ class GnssGeofenceProvider extends IGpsGeofenceHardware.Stub { mNative.pauseGeofence(entry.geofenceId); } } - }); - } - - private boolean runOnHandlerThread(Callable<Boolean> callable) { - FutureTask<Boolean> futureTask = new FutureTask<>(callable); - mHandler.post(futureTask); - try { - return futureTask.get(); - } catch (InterruptedException | ExecutionException e) { - Log.e(TAG, "Failed running callable.", e); } - return false; } @Override public boolean isHardwareGeofenceSupported() { - return runOnHandlerThread(mNative::isGeofenceSupported); + synchronized (mLock) { + return mNative.isGeofenceSupported(); + } } @Override public boolean addCircularHardwareGeofence(int geofenceId, double latitude, double longitude, double radius, int lastTransition, int monitorTransitions, int notificationResponsiveness, int unknownTimer) { - return runOnHandlerThread(() -> { + synchronized (mLock) { boolean added = mNative.addGeofence(geofenceId, latitude, longitude, radius, lastTransition, monitorTransitions, notificationResponsiveness, unknownTimer); @@ -105,23 +90,23 @@ class GnssGeofenceProvider extends IGpsGeofenceHardware.Stub { mGeofenceEntries.put(geofenceId, entry); } return added; - }); + } } @Override public boolean removeHardwareGeofence(int geofenceId) { - return runOnHandlerThread(() -> { + synchronized (mLock) { boolean removed = mNative.removeGeofence(geofenceId); if (removed) { mGeofenceEntries.remove(geofenceId); } return removed; - }); + } } @Override public boolean pauseHardwareGeofence(int geofenceId) { - return runOnHandlerThread(() -> { + synchronized (mLock) { boolean paused = mNative.pauseGeofence(geofenceId); if (paused) { GeofenceEntry entry = mGeofenceEntries.get(geofenceId); @@ -130,12 +115,12 @@ class GnssGeofenceProvider extends IGpsGeofenceHardware.Stub { } } return paused; - }); + } } @Override public boolean resumeHardwareGeofence(int geofenceId, int monitorTransitions) { - return runOnHandlerThread(() -> { + synchronized (mLock) { boolean resumed = mNative.resumeGeofence(geofenceId, monitorTransitions); if (resumed) { GeofenceEntry entry = mGeofenceEntries.get(geofenceId); @@ -145,7 +130,7 @@ class GnssGeofenceProvider extends IGpsGeofenceHardware.Stub { } } return resumed; - }); + } } @VisibleForTesting diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index d5e4681a0d90..29e1878b739a 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -31,10 +31,7 @@ import android.location.FusedBatchOptions; import android.location.GnssMeasurementsEvent; import android.location.GnssNavigationMessage; import android.location.GnssStatus; -import android.location.IGnssStatusListener; -import android.location.IGnssStatusProvider; import android.location.IGpsGeofenceHardware; -import android.location.ILocationManager; import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationListener; @@ -84,6 +81,10 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -97,8 +98,18 @@ import java.util.Properties; * * {@hide} */ -public class GnssLocationProvider extends LocationProviderInterface - implements InjectNtpTimeCallback, GnssSatelliteBlacklistCallback { +public class GnssLocationProvider extends AbstractLocationProvider implements + InjectNtpTimeCallback, + GnssSatelliteBlacklistCallback { + + /** + * Indicates that this method is a native entry point. Useful purely for IDEs which can + * understand entry points, and thus eliminate incorrect warnings about methods not used. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.SOURCE) + private @interface NativeEntryPoint { + } private static final String TAG = "GnssLocationProvider"; @@ -249,7 +260,7 @@ public class GnssLocationProvider extends LocationProviderInterface } public void set(int svCount, int meanCn0, int maxCn0) { - synchronized(this) { + synchronized (this) { mSvCount = svCount; mMeanCn0 = meanCn0; mMaxCn0 = maxCn0; @@ -258,7 +269,7 @@ public class GnssLocationProvider extends LocationProviderInterface } public void reset() { - set(0,0,0); + set(0, 0, 0); } // Also used by outside methods to add to other bundles @@ -314,7 +325,7 @@ public class GnssLocationProvider extends LocationProviderInterface MAX_RETRY_INTERVAL); // true if we are enabled, protected by this - private boolean mEnabled; + private boolean mEnabled = true; // states for injecting ntp and downloading xtra data private static final int STATE_PENDING_NETWORK = 0; @@ -328,9 +339,6 @@ public class GnssLocationProvider extends LocationProviderInterface // true if GPS is navigating private boolean mNavigating; - // true if GPS engine is on - private boolean mEngineOn; - // requested frequency of fixes, in milliseconds private int mFixInterval = 1000; @@ -380,9 +388,8 @@ public class GnssLocationProvider extends LocationProviderInterface private boolean mSuplEsEnabled = false; private final Context mContext; - private final ILocationManager mILocationManager; private final LocationExtras mLocationExtras = new LocationExtras(); - private final GnssStatusListenerHelper mListenerHelper; + private final GnssStatusListenerHelper mGnssStatusListenerHelper; private final GnssSatelliteBlacklistHelper mGnssSatelliteBlacklistHelper; private final GnssMeasurementsProvider mGnssMeasurementsProvider; private final GnssNavigationMessageProvider mGnssNavigationMessageProvider; @@ -443,20 +450,8 @@ public class GnssLocationProvider extends LocationProviderInterface // GNSS Metrics private GnssMetrics mGnssMetrics; - private final IGnssStatusProvider mGnssStatusProvider = new IGnssStatusProvider.Stub() { - @Override - public void registerGnssStatusCallback(IGnssStatusListener callback) { - mListenerHelper.addListener(callback); - } - - @Override - public void unregisterGnssStatusCallback(IGnssStatusListener callback) { - mListenerHelper.removeListener(callback); - } - }; - - public IGnssStatusProvider getGnssStatusProvider() { - return mGnssStatusProvider; + public GnssStatusListenerHelper getGnssStatusProvider() { + return mGnssStatusListenerHelper; } public IGpsGeofenceHardware getGpsGeofenceProxy() { @@ -479,17 +474,22 @@ public class GnssLocationProvider extends LocationProviderInterface return; } - if (action.equals(ALARM_WAKEUP)) { - startNavigating(false); - } else if (action.equals(ALARM_TIMEOUT)) { - hibernate(); - } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(action) - || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action) - || Intent.ACTION_SCREEN_OFF.equals(action) - || Intent.ACTION_SCREEN_ON.equals(action)) { - updateLowPowerMode(); - } else if (action.equals(SIM_STATE_CHANGED)) { - subscriptionOrSimChanged(context); + switch (action) { + case ALARM_WAKEUP: + startNavigating(false); + break; + case ALARM_TIMEOUT: + hibernate(); + break; + case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED: + case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: + case Intent.ACTION_SCREEN_OFF: + case Intent.ACTION_SCREEN_ON: + updateLowPowerMode(); + break; + case SIM_STATE_CHANGED: + subscriptionOrSimChanged(context); + break; } } }; @@ -507,9 +507,7 @@ public class GnssLocationProvider extends LocationProviderInterface */ @Override public void onUpdateSatelliteBlacklist(int[] constellations, int[] svids) { - mHandler.post(()->{ - native_set_satellite_blacklist(constellations, svids); - }); + mHandler.post(() -> native_set_satellite_blacklist(constellations, svids)); } private void subscriptionOrSimChanged(Context context) { @@ -572,7 +570,7 @@ public class GnssLocationProvider extends LocationProviderInterface } interface SetCarrierProperty { - public boolean set(int value); + boolean set(int value); } private void reloadGpsProperties(Context context, Properties properties) { @@ -587,7 +585,7 @@ public class GnssLocationProvider extends LocationProviderInterface /* * Overlay carrier properties from a debug configuration file. */ - loadPropertiesFromFile(DEBUG_PROPERTIES_FILE, properties); + loadPropertiesFromFile(properties); // TODO: we should get rid of C2K specific setting. setSuplHostPort(properties.getProperty("SUPL_HOST"), properties.getProperty("SUPL_PORT")); @@ -603,15 +601,15 @@ public class GnssLocationProvider extends LocationProviderInterface if (native_is_gnss_configuration_supported()) { Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>() { { - put("SUPL_VER", (val) -> native_set_supl_version(val)); - put("SUPL_MODE", (val) -> native_set_supl_mode(val)); - put("SUPL_ES", (val) -> native_set_supl_es(val)); - put("LPP_PROFILE", (val) -> native_set_lpp_profile(val)); + put("SUPL_VER", GnssLocationProvider::native_set_supl_version); + put("SUPL_MODE", GnssLocationProvider::native_set_supl_mode); + put("SUPL_ES", GnssLocationProvider::native_set_supl_es); + put("LPP_PROFILE", GnssLocationProvider::native_set_lpp_profile); put("A_GLONASS_POS_PROTOCOL_SELECT", - (val) -> native_set_gnss_pos_protocol_select(val)); + GnssLocationProvider::native_set_gnss_pos_protocol_select); put("USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL", - (val) -> native_set_emergency_supl_pdn(val)); - put("GPS_LOCK", (val) -> native_set_gps_lock(val)); + GnssLocationProvider::native_set_emergency_supl_pdn); + put("GPS_LOCK", GnssLocationProvider::native_set_gps_lock); } }; @@ -622,7 +620,7 @@ public class GnssLocationProvider extends LocationProviderInterface try { int propertyValueInt = Integer.decode(propertyValueString); boolean result = entry.getValue().set(propertyValueInt); - if (result == false) { + if (!result) { Log.e(TAG, "Unable to set " + propertyName); } } catch (NumberFormatException e) { @@ -664,10 +662,9 @@ public class GnssLocationProvider extends LocationProviderInterface } } - private boolean loadPropertiesFromFile(String filename, - Properties properties) { + private void loadPropertiesFromFile(Properties properties) { try { - File file = new File(filename); + File file = new File(DEBUG_PROPERTIES_FILE); FileInputStream stream = null; try { stream = new FileInputStream(file); @@ -677,16 +674,15 @@ public class GnssLocationProvider extends LocationProviderInterface } } catch (IOException e) { - if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + filename); - return false; + if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + DEBUG_PROPERTIES_FILE); } - return true; } - public GnssLocationProvider(Context context, ILocationManager ilocationManager, + public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager, Looper looper) { + super(locationProviderManager, true); + mContext = context; - mILocationManager = ilocationManager; // Create a wake lock mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -730,7 +726,7 @@ public class GnssLocationProvider extends LocationProviderInterface mNetInitiatedListener, mSuplEsEnabled); - mListenerHelper = new GnssStatusListenerHelper(mHandler) { + mGnssStatusListenerHelper = new GnssStatusListenerHelper(mContext, mHandler) { @Override protected boolean isAvailableInPlatform() { return isSupported(); @@ -749,7 +745,7 @@ public class GnssLocationProvider extends LocationProviderInterface } }; - mGnssNavigationMessageProvider = new GnssNavigationMessageProvider(mHandler) { + mGnssNavigationMessageProvider = new GnssNavigationMessageProvider(mContext, mHandler) { @Override protected boolean isGpsEnabled() { return isEnabled(); @@ -762,20 +758,21 @@ public class GnssLocationProvider extends LocationProviderInterface looper, this); mHandler.post(mGnssSatelliteBlacklistHelper::updateSatelliteBlacklist); mGnssBatchingProvider = new GnssBatchingProvider(); - mGnssGeofenceProvider = new GnssGeofenceProvider(looper); - } + mGnssGeofenceProvider = new GnssGeofenceProvider(); - /** - * Returns the name of this provider. - */ - @Override - public String getName() { - return LocationManager.GPS_PROVIDER; - } + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_SHUTDOWN); + mContext.registerReceiverAsUser(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (getSendingUserId() == UserHandle.USER_ALL) { + mEnabled = false; + handleDisable(); + } + } + }, UserHandle.ALL, intentFilter, null, mHandler); - @Override - public ProviderProperties getProperties() { - return PROPERTIES; + setProperties(PROPERTIES); } /** @@ -840,9 +837,9 @@ public class GnssLocationProvider extends LocationProviderInterface locationManager.requestLocationUpdates(provider, LOCATION_UPDATE_MIN_TIME_INTERVAL_MILLIS, /*minDistance=*/ 0, locationListener, mHandler.getLooper()); - locationListener.numLocationUpdateRequest++; + locationListener.mNumLocationUpdateRequest++; mHandler.postDelayed(() -> { - if (--locationListener.numLocationUpdateRequest == 0) { + if (--locationListener.mNumLocationUpdateRequest == 0) { Log.i(TAG, String.format("Removing location updates from %s provider.", provider)); locationManager.removeUpdates(locationListener); @@ -905,43 +902,40 @@ public class GnssLocationProvider extends LocationProviderInterface // hold wake lock while task runs mDownloadXtraWakeLock.acquire(DOWNLOAD_XTRA_DATA_TIMEOUT_MS); Log.i(TAG, "WakeLock acquired by handleDownloadXtraData()"); - AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { - @Override - public void run() { - GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mProperties); - byte[] data = xtraDownloader.downloadXtraData(); - if (data != null) { - if (DEBUG) Log.d(TAG, "calling native_inject_xtra_data"); - native_inject_xtra_data(data, data.length); - mXtraBackOff.reset(); - } + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mProperties); + byte[] data = xtraDownloader.downloadXtraData(); + if (data != null) { + if (DEBUG) Log.d(TAG, "calling native_inject_xtra_data"); + native_inject_xtra_data(data, data.length); + mXtraBackOff.reset(); + } - sendMessage(DOWNLOAD_XTRA_DATA_FINISHED, 0, null); + sendMessage(DOWNLOAD_XTRA_DATA_FINISHED, 0, null); - if (data == null) { - // try again later - // since this is delayed and not urgent we do not hold a wake lock here - mHandler.sendEmptyMessageDelayed(DOWNLOAD_XTRA_DATA, - mXtraBackOff.nextBackoffMillis()); - } + if (data == null) { + // try again later + // since this is delayed and not urgent we do not hold a wake lock here + mHandler.sendEmptyMessageDelayed(DOWNLOAD_XTRA_DATA, + mXtraBackOff.nextBackoffMillis()); + } - // Release wake lock held by task, synchronize on mLock in case multiple - // download tasks overrun. - synchronized (mLock) { - if (mDownloadXtraWakeLock.isHeld()) { - // This wakelock may have time-out, if a timeout was specified. - // Catch (and ignore) any timeout exceptions. - try { - mDownloadXtraWakeLock.release(); - if (DEBUG) Log.d(TAG, "WakeLock released by handleDownloadXtraData()"); - } catch (Exception e) { - Log.i(TAG, "Wakelock timeout & release race exception in " - + "handleDownloadXtraData()", e); - } - } else { - Log.e(TAG, "WakeLock expired before release in " - + "handleDownloadXtraData()"); + // Release wake lock held by task, synchronize on mLock in case multiple + // download tasks overrun. + synchronized (mLock) { + if (mDownloadXtraWakeLock.isHeld()) { + // This wakelock may have time-out, if a timeout was specified. + // Catch (and ignore) any timeout exceptions. + try { + mDownloadXtraWakeLock.release(); + if (DEBUG) Log.d(TAG, "WakeLock released by handleDownloadXtraData()"); + } catch (Exception e) { + Log.i(TAG, "Wakelock timeout & release race exception in " + + "handleDownloadXtraData()", e); } + } else { + Log.e(TAG, "WakeLock expired before release in " + + "handleDownloadXtraData()"); } } }); @@ -954,21 +948,6 @@ public class GnssLocationProvider extends LocationProviderInterface } } - /** - * Enables this provider. When enabled, calls to getStatus() - * must be handled. Hardware may be started up - * when the provider is enabled. - */ - @Override - public void enable() { - synchronized (mLock) { - if (mEnabled) return; - mEnabled = true; - } - - sendMessage(ENABLE, 1, null); - } - private void setSuplHostPort(String hostString, String portString) { if (hostString != null) { mSuplServerHost = hostString; @@ -1052,21 +1031,6 @@ public class GnssLocationProvider extends LocationProviderInterface } } - /** - * Disables this provider. When disabled, calls to getStatus() - * need not be handled. Hardware may be shut - * down while the provider is disabled. - */ - @Override - public void disable() { - synchronized (mLock) { - if (!mEnabled) return; - mEnabled = false; - } - - sendMessage(ENABLE, 0, null); - } - private void handleDisable() { if (DEBUG) Log.d(TAG, "handleDisable"); @@ -1083,7 +1047,6 @@ public class GnssLocationProvider extends LocationProviderInterface mGnssNavigationMessageProvider.onGpsEnabledChanged(); } - @Override public boolean isEnabled() { synchronized (mLock) { return mEnabled; @@ -1147,7 +1110,7 @@ public class GnssLocationProvider extends LocationProviderInterface updateClientUids(mWorkSource); mFixInterval = (int) mProviderRequest.interval; - mLowPowerMode = (boolean) mProviderRequest.lowPowerMode; + mLowPowerMode = mProviderRequest.lowPowerMode; // check for overflow if (mFixInterval != mProviderRequest.interval) { Log.w(TAG, "interval overflow: " + mProviderRequest.interval); @@ -1171,7 +1134,8 @@ public class GnssLocationProvider extends LocationProviderInterface // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT // and our fix interval is not short mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, mTimeoutIntent); } + SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, mTimeoutIntent); + } } } else { updateClientUids(new WorkSource()); @@ -1220,16 +1184,14 @@ public class GnssLocationProvider extends LocationProviderInterface List<WorkChain> goneChains = diffs[1]; if (newChains != null) { - for (int i = 0; i < newChains.size(); ++i) { - final WorkChain newChain = newChains.get(i); + for (WorkChain newChain : newChains) { mAppOps.startOpNoThrow(AppOpsManager.OP_GPS, newChain.getAttributionUid(), newChain.getAttributionTag()); } } if (goneChains != null) { - for (int i = 0; i < goneChains.size(); i++) { - final WorkChain goneChain = goneChains.get(i); + for (WorkChain goneChain : goneChains) { mAppOps.finishOp(AppOpsManager.OP_GPS, goneChain.getAttributionUid(), goneChain.getAttributionTag()); } @@ -1262,32 +1224,27 @@ public class GnssLocationProvider extends LocationProviderInterface } @Override - public boolean sendExtraCommand(String command, Bundle extras) { + public void sendExtraCommand(String command, Bundle extras) { long identity = Binder.clearCallingIdentity(); try { - boolean result = false; - if ("delete_aiding_data".equals(command)) { - result = deleteAidingData(extras); + deleteAidingData(extras); } else if ("force_time_injection".equals(command)) { requestUtcTime(); - result = true; } else if ("force_xtra_injection".equals(command)) { if (mSupportsXtra) { xtraDownloadRequest(); - result = true; } } else { Log.w(TAG, "sendExtraCommand: unknown command " + command); } - return result; } finally { Binder.restoreCallingIdentity(identity); } } - private boolean deleteAidingData(Bundle extras) { + private void deleteAidingData(Bundle extras) { int flags; if (extras == null) { @@ -1311,10 +1268,7 @@ public class GnssLocationProvider extends LocationProviderInterface if (flags != 0) { native_delete_aiding_data(flags); - return true; } - - return false; } private void startNavigating(boolean singleShot) { @@ -1358,7 +1312,7 @@ public class GnssLocationProvider extends LocationProviderInterface } int interval = (hasCapability(GPS_CAPABILITY_SCHEDULING) ? mFixInterval : 1000); - mLowPowerMode = (boolean) mProviderRequest.lowPowerMode; + mLowPowerMode = mProviderRequest.lowPowerMode; if (!setPositionMode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, interval, 0, 0, mLowPowerMode)) { mStarted = false; @@ -1415,10 +1369,7 @@ public class GnssLocationProvider extends LocationProviderInterface return ((mEngineCapabilities & capability) != 0); } - - /** - * called from native code to update our position. - */ + @NativeEntryPoint private void reportLocation(boolean hasLatLong, Location location) { sendMessage(REPORT_LOCATION, hasLatLong ? 1 : 0, location); } @@ -1444,11 +1395,7 @@ public class GnssLocationProvider extends LocationProviderInterface location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); location.setExtras(mLocationExtras.getBundle()); - try { - mILocationManager.reportLocation(location, false); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling reportLocation"); - } + reportLocation(location); if (mStarted) { mGnssMetrics.logReceivedLocationStatus(hasLatLong); @@ -1473,7 +1420,7 @@ public class GnssLocationProvider extends LocationProviderInterface } // notify status listeners - mListenerHelper.onFirstFix(mTimeToFirstFix); + mGnssStatusListenerHelper.onFirstFix(mTimeToFirstFix); } if (mSingleShot) { @@ -1482,7 +1429,8 @@ public class GnssLocationProvider extends LocationProviderInterface if (mStarted && mStatus != LocationProvider.AVAILABLE) { // For devices that use framework scheduling, a timer may be set to ensure we don't - // spend too much power searching for a location, when the requested update rate is slow. + // spend too much power searching for a location, when the requested update rate is + // slow. // As we just recievied a location, we'll cancel that timer. if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mFixInterval < NO_FIX_TIMEOUT) { mAlarmManager.cancel(mTimeoutIntent); @@ -1502,9 +1450,7 @@ public class GnssLocationProvider extends LocationProviderInterface } } - /** - * called from native code to update our status - */ + @NativeEntryPoint private void reportStatus(int status) { if (DEBUG) Log.v(TAG, "reportStatus status: " + status); @@ -1512,22 +1458,19 @@ public class GnssLocationProvider extends LocationProviderInterface switch (status) { case GPS_STATUS_SESSION_BEGIN: mNavigating = true; - mEngineOn = true; break; case GPS_STATUS_SESSION_END: mNavigating = false; break; case GPS_STATUS_ENGINE_ON: - mEngineOn = true; break; case GPS_STATUS_ENGINE_OFF: - mEngineOn = false; mNavigating = false; break; } if (wasNavigating != mNavigating) { - mListenerHelper.onStatusChanged(mNavigating); + mGnssStatusListenerHelper.onStatusChanged(mNavigating); // send an intent to notify that the GPS has been enabled or disabled Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION); @@ -1538,17 +1481,15 @@ public class GnssLocationProvider extends LocationProviderInterface // Helper class to carry data to handler for reportSvStatus private static class SvStatusInfo { - public int mSvCount; - public int[] mSvidWithFlags; - public float[] mCn0s; - public float[] mSvElevations; - public float[] mSvAzimuths; - public float[] mSvCarrierFreqs; + private int mSvCount; + private int[] mSvidWithFlags; + private float[] mCn0s; + private float[] mSvElevations; + private float[] mSvAzimuths; + private float[] mSvCarrierFreqs; } - /** - * called from native code to update SV info - */ + @NativeEntryPoint private void reportSvStatus(int svCount, int[] svidWithFlags, float[] cn0s, float[] svElevations, float[] svAzimuths, float[] svCarrierFreqs) { SvStatusInfo svStatusInfo = new SvStatusInfo(); @@ -1563,7 +1504,7 @@ public class GnssLocationProvider extends LocationProviderInterface } private void handleReportSvStatus(SvStatusInfo info) { - mListenerHelper.onSvStatusChanged( + mGnssStatusListenerHelper.onSvStatusChanged( info.mSvCount, info.mSvidWithFlags, info.mCn0s, @@ -1622,75 +1563,52 @@ public class GnssLocationProvider extends LocationProviderInterface } } - /** - * called from native code to update AGPS status - */ + @NativeEntryPoint private void reportAGpsStatus(int type, int status, byte[] ipaddr) { mNetworkConnectivityHandler.onReportAGpsStatus(type, status, ipaddr); } - /** - * called from native code to report NMEA data received - */ + @NativeEntryPoint private void reportNmea(long timestamp) { if (!mItarSpeedLimitExceeded) { int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length); String nmea = new String(mNmeaBuffer, 0 /* offset */, length); - mListenerHelper.onNmeaReceived(timestamp, nmea); + mGnssStatusListenerHelper.onNmeaReceived(timestamp, nmea); } } - /** - * called from native code - GNSS measurements callback - */ + @NativeEntryPoint private void reportMeasurementData(GnssMeasurementsEvent event) { if (!mItarSpeedLimitExceeded) { // send to handler to allow native to return quickly - mHandler.post(new Runnable() { - @Override - public void run() { - mGnssMeasurementsProvider.onMeasurementsAvailable(event); - } - }); + mHandler.post(() -> mGnssMeasurementsProvider.onMeasurementsAvailable(event)); } } - /** - * called from native code - GNSS navigation message callback - */ + @NativeEntryPoint private void reportNavigationMessage(GnssNavigationMessage event) { if (!mItarSpeedLimitExceeded) { // send to handler to allow native to return quickly - mHandler.post(new Runnable() { - @Override - public void run() { - mGnssNavigationMessageProvider.onNavigationMessageAvailable(event); - } - }); + mHandler.post(() -> mGnssNavigationMessageProvider.onNavigationMessageAvailable(event)); } } - /** - * called from native code to inform us what the GPS engine capabilities are - */ + @NativeEntryPoint private void setEngineCapabilities(final int capabilities) { // send to handler thread for fast native return, and in-order handling - mHandler.post(new Runnable() { - @Override - public void run() { - mEngineCapabilities = capabilities; - - if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) { - mNtpTimeHelper.enablePeriodicTimeInjection(); - requestUtcTime(); - } + mHandler.post(() -> { + mEngineCapabilities = capabilities; - mGnssMeasurementsProvider.onCapabilitiesUpdated(hasCapability( - GPS_CAPABILITY_MEASUREMENTS)); - mGnssNavigationMessageProvider.onCapabilitiesUpdated(hasCapability( - GPS_CAPABILITY_NAV_MESSAGES)); - restartRequests(); + if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) { + mNtpTimeHelper.enablePeriodicTimeInjection(); + requestUtcTime(); } + + mGnssMeasurementsProvider.onCapabilitiesUpdated(hasCapability( + GPS_CAPABILITY_MEASUREMENTS)); + mGnssNavigationMessageProvider.onCapabilitiesUpdated(hasCapability( + GPS_CAPABILITY_NAV_MESSAGES)); + restartRequests(); }); } @@ -1710,27 +1628,21 @@ public class GnssLocationProvider extends LocationProviderInterface updateRequirements(); } - /** - * Called from native code to inform us the hardware year. - */ + @NativeEntryPoint private void setGnssYearOfHardware(final int yearOfHardware) { // mHardwareYear is simply set here, to be read elsewhere, and is volatile for safe sync if (DEBUG) Log.d(TAG, "setGnssYearOfHardware called with " + yearOfHardware); mHardwareYear = yearOfHardware; } - /** - * Called from native code to inform us the hardware model name. - */ + @NativeEntryPoint private void setGnssHardwareModelName(final String modelName) { // mHardwareModelName is simply set here, to be read elsewhere, and volatile for safe sync if (DEBUG) Log.d(TAG, "setGnssModelName called with " + modelName); mHardwareModelName = modelName; } - /** - * Called from native code to inform us GNSS HAL service died. - */ + @NativeEntryPoint private void reportGnssServiceDied() { if (DEBUG) Log.d(TAG, "reportGnssServiceDied"); mHandler.post(() -> { @@ -1750,6 +1662,7 @@ public class GnssLocationProvider extends LocationProviderInterface * Returns the year of underlying GPS hardware. */ int getGnssYearOfHardware(); + /** * Returns the model name of underlying GPS hardware. */ @@ -1765,6 +1678,7 @@ public class GnssLocationProvider extends LocationProviderInterface public int getGnssYearOfHardware() { return mHardwareYear; } + @Override public String getGnssHardwareModelName() { return mHardwareModelName; @@ -1790,32 +1704,19 @@ public class GnssLocationProvider extends LocationProviderInterface * @hide */ public GnssMetricsProvider getGnssMetricsProvider() { - return new GnssMetricsProvider() { - @Override - public String getGnssMetricsAsProtoString() { - return mGnssMetrics.dumpGnssMetricsAsProtoString(); - } - }; + return () -> mGnssMetrics.dumpGnssMetricsAsProtoString(); } - /** - * called from native code - GNSS location batch callback - */ + @NativeEntryPoint private void reportLocationBatch(Location[] locationArray) { List<Location> locations = new ArrayList<>(Arrays.asList(locationArray)); if (DEBUG) { Log.d(TAG, "Location batch of size " + locationArray.length + " reported"); } - try { - mILocationManager.reportLocationBatch(locations); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling reportLocationBatch"); - } + reportLocation(locations); } - /** - * called from native code to request XTRA data - */ + @NativeEntryPoint private void xtraDownloadRequest() { if (DEBUG) Log.d(TAG, "xtraDownloadRequest"); sendMessage(DOWNLOAD_XTRA_DATA, 0, null); @@ -1824,7 +1725,7 @@ public class GnssLocationProvider extends LocationProviderInterface /** * Converts the GPS HAL status to the internal Geofence Hardware status. */ - private int getGeofenceStatus(int status) { + private static int getGeofenceStatus(int status) { switch (status) { case GPS_GEOFENCE_OPERATION_SUCCESS: return GeofenceHardware.GEOFENCE_SUCCESS; @@ -1843,81 +1744,80 @@ public class GnssLocationProvider extends LocationProviderInterface } } - /** - * Called from native to report GPS Geofence transition - * All geofence callbacks are called on the same thread - */ + @NativeEntryPoint private void reportGeofenceTransition(int geofenceId, Location location, int transition, long transitionTimestamp) { - if (mGeofenceHardwareImpl == null) { - mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); - } + mHandler.post(() -> { + if (mGeofenceHardwareImpl == null) { + mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); + } - mGeofenceHardwareImpl.reportGeofenceTransition( - geofenceId, - location, - transition, - transitionTimestamp, - GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, - FusedBatchOptions.SourceTechnologies.GNSS); + mGeofenceHardwareImpl.reportGeofenceTransition( + geofenceId, + location, + transition, + transitionTimestamp, + GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, + FusedBatchOptions.SourceTechnologies.GNSS); + }); } - /** - * called from native code to report GPS status change. - */ + @NativeEntryPoint private void reportGeofenceStatus(int status, Location location) { - if (mGeofenceHardwareImpl == null) { - mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); - } - int monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE; - if (status == GPS_GEOFENCE_AVAILABLE) { - monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE; - } - mGeofenceHardwareImpl.reportGeofenceMonitorStatus( - GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, - monitorStatus, - location, - FusedBatchOptions.SourceTechnologies.GNSS); + mHandler.post(() -> { + if (mGeofenceHardwareImpl == null) { + mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); + } + int monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE; + if (status == GPS_GEOFENCE_AVAILABLE) { + monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE; + } + mGeofenceHardwareImpl.reportGeofenceMonitorStatus( + GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, + monitorStatus, + location, + FusedBatchOptions.SourceTechnologies.GNSS); + }); } - /** - * called from native code - Geofence Add callback - */ + @NativeEntryPoint private void reportGeofenceAddStatus(int geofenceId, int status) { - if (mGeofenceHardwareImpl == null) { - mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); - } - mGeofenceHardwareImpl.reportGeofenceAddStatus(geofenceId, getGeofenceStatus(status)); + mHandler.post(() -> { + if (mGeofenceHardwareImpl == null) { + mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); + } + mGeofenceHardwareImpl.reportGeofenceAddStatus(geofenceId, getGeofenceStatus(status)); + }); } - /** - * called from native code - Geofence Remove callback - */ + @NativeEntryPoint private void reportGeofenceRemoveStatus(int geofenceId, int status) { - if (mGeofenceHardwareImpl == null) { - mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); - } - mGeofenceHardwareImpl.reportGeofenceRemoveStatus(geofenceId, getGeofenceStatus(status)); + mHandler.post(() -> { + if (mGeofenceHardwareImpl == null) { + mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); + } + mGeofenceHardwareImpl.reportGeofenceRemoveStatus(geofenceId, getGeofenceStatus(status)); + }); } - /** - * called from native code - Geofence Pause callback - */ + @NativeEntryPoint private void reportGeofencePauseStatus(int geofenceId, int status) { - if (mGeofenceHardwareImpl == null) { - mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); - } - mGeofenceHardwareImpl.reportGeofencePauseStatus(geofenceId, getGeofenceStatus(status)); + mHandler.post(() -> { + if (mGeofenceHardwareImpl == null) { + mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); + } + mGeofenceHardwareImpl.reportGeofencePauseStatus(geofenceId, getGeofenceStatus(status)); + }); } - /** - * called from native code - Geofence Resume callback - */ + @NativeEntryPoint private void reportGeofenceResumeStatus(int geofenceId, int status) { - if (mGeofenceHardwareImpl == null) { - mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); - } - mGeofenceHardwareImpl.reportGeofenceResumeStatus(geofenceId, getGeofenceStatus(status)); + mHandler.post(() -> { + if (mGeofenceHardwareImpl == null) { + mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); + } + mGeofenceHardwareImpl.reportGeofenceResumeStatus(geofenceId, getGeofenceStatus(status)); + }); } //============================================================= @@ -1942,7 +1842,8 @@ public class GnssLocationProvider extends LocationProviderInterface return mNetInitiatedListener; } - // Called by JNI function to report an NI request. + /** Reports a NI notification. */ + @NativeEntryPoint public void reportNiNotification( int notificationId, int niType, @@ -1985,11 +1886,10 @@ public class GnssLocationProvider extends LocationProviderInterface } /** - * Called from native code to request set id info. * We should be careful about receiving null string from the TelephonyManager, * because sending null String to JNI function would cause a crash. */ - + @NativeEntryPoint private void requestSetID(int flags) { TelephonyManager phone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); @@ -2018,9 +1918,7 @@ public class GnssLocationProvider extends LocationProviderInterface native_agps_set_id(type, data); } - /** - * Called from native code to request location info. - */ + @NativeEntryPoint private void requestLocation(boolean independentFromGnss) { if (DEBUG) { Log.d(TAG, "requestLocation. independentFromGnss: " + independentFromGnss); @@ -2028,17 +1926,13 @@ public class GnssLocationProvider extends LocationProviderInterface sendMessage(REQUEST_LOCATION, 0, independentFromGnss); } - /** - * Called from native code to request utc time info - */ + @NativeEntryPoint private void requestUtcTime() { if (DEBUG) Log.d(TAG, "utcTimeRequest"); sendMessage(INJECT_NTP_TIME, 0, null); } - /** - * Called from native code to request reference location info - */ + @NativeEntryPoint private void requestRefLocation() { TelephonyManager phone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); @@ -2092,11 +1986,7 @@ public class GnssLocationProvider extends LocationProviderInterface int message = msg.what; switch (message) { case ENABLE: - if (msg.arg1 == 1) { - handleEnable(); - } else { - handleDisable(); - } + handleEnable(); break; case SET_REQUEST: GpsRequest gpsRequest = (GpsRequest) msg.obj; @@ -2141,7 +2031,8 @@ public class GnssLocationProvider extends LocationProviderInterface } /** - * This method is bound to {@link #GnssLocationProvider(Context, ILocationManager, Looper)}. + * This method is bound to {@link #GnssLocationProvider(Context, LocationProviderManager, + * Looper)}. * It is in charge of loading properties and registering for events that will be posted to * this handler. */ @@ -2194,12 +2085,11 @@ public class GnssLocationProvider extends LocationProviderInterface (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); long minTime = 0; float minDistance = 0; - boolean oneShot = false; LocationRequest request = LocationRequest.createFromDeprecatedProvider( LocationManager.PASSIVE_PROVIDER, minTime, minDistance, - oneShot); + false); // Don't keep track of this request since it's done on behalf of other clients // (which are kept track of separately). request.setHideFromAppOps(true); @@ -2207,11 +2097,14 @@ public class GnssLocationProvider extends LocationProviderInterface request, new NetworkLocationListener(), getLooper()); + + // enable gps provider, it will never be disabled (legacy behavior) + sendEmptyMessage(ENABLE); } } private abstract class LocationChangeListener implements LocationListener { - int numLocationUpdateRequest; + private int mNumLocationUpdateRequest; @Override public void onStatusChanged(String provider, int status, Bundle extras) { diff --git a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java index 0add86315487..3e2ba87a033e 100644 --- a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java @@ -38,7 +38,6 @@ public abstract class GnssMeasurementsProvider extends private static final String TAG = "GnssMeasurementsProvider"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private final Context mContext; private final GnssMeasurementProviderNative mNative; private boolean mIsCollectionStarted; @@ -51,8 +50,7 @@ public abstract class GnssMeasurementsProvider extends @VisibleForTesting GnssMeasurementsProvider(Context context, Handler handler, GnssMeasurementProviderNative aNative) { - super(handler, TAG); - mContext = context; + super(context, handler, TAG); mNative = aNative; } @@ -98,9 +96,13 @@ public abstract class GnssMeasurementsProvider extends } public void onMeasurementsAvailable(final GnssMeasurementsEvent event) { - ListenerOperation<IGnssMeasurementsListener> operation = - listener -> listener.onGnssMeasurementsReceived(event); - foreach(operation); + foreach((IGnssMeasurementsListener listener, int uid, String packageName) -> { + if (!hasPermission(uid, packageName)) { + logPermissionDisabledEventNotReported(TAG, packageName, "GNSS measurements"); + return; + } + listener.onGnssMeasurementsReceived(event); + }); } public void onCapabilitiesUpdated(boolean isGnssMeasurementsSupported) { @@ -149,7 +151,8 @@ public abstract class GnssMeasurementsProvider extends } @Override - public void execute(IGnssMeasurementsListener listener) throws RemoteException { + public void execute(IGnssMeasurementsListener listener, + int uid, String packageName) throws RemoteException { listener.onStatusChanged(mStatus); } } diff --git a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java index 1b4fd187051a..679919f8fc67 100644 --- a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java +++ b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java @@ -16,6 +16,7 @@ package com.android.server.location; +import android.content.Context; import android.location.GnssNavigationMessage; import android.location.IGnssNavigationMessageListener; import android.os.Handler; @@ -39,13 +40,14 @@ public abstract class GnssNavigationMessageProvider private final GnssNavigationMessageProviderNative mNative; private boolean mCollectionStarted; - protected GnssNavigationMessageProvider(Handler handler) { - this(handler, new GnssNavigationMessageProviderNative()); + protected GnssNavigationMessageProvider(Context context, Handler handler) { + this(context, handler, new GnssNavigationMessageProviderNative()); } @VisibleForTesting - GnssNavigationMessageProvider(Handler handler, GnssNavigationMessageProviderNative aNative) { - super(handler, TAG); + GnssNavigationMessageProvider(Context context, Handler handler, + GnssNavigationMessageProviderNative aNative) { + super(context, handler, TAG); mNative = aNative; } @@ -84,15 +86,10 @@ public abstract class GnssNavigationMessageProvider } public void onNavigationMessageAvailable(final GnssNavigationMessage event) { - ListenerOperation<IGnssNavigationMessageListener> operation = - new ListenerOperation<IGnssNavigationMessageListener>() { - @Override - public void execute(IGnssNavigationMessageListener listener) - throws RemoteException { - listener.onGnssNavigationMessageReceived(event); - } - }; - foreach(operation); + foreach((IGnssNavigationMessageListener listener, int uid, String packageName) -> { + listener.onGnssNavigationMessageReceived(event); + } + ); } public void onCapabilitiesUpdated(boolean isGnssNavigationMessageSupported) { @@ -138,7 +135,8 @@ public abstract class GnssNavigationMessageProvider } @Override - public void execute(IGnssNavigationMessageListener listener) throws RemoteException { + public void execute(IGnssNavigationMessageListener listener, + int uid, String packageName) throws RemoteException { listener.onStatusChanged(mStatus); } } diff --git a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java index 124220f17f1f..454dbddc406a 100644 --- a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java +++ b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java @@ -16,16 +16,20 @@ package com.android.server.location; +import android.content.Context; import android.location.IGnssStatusListener; import android.os.Handler; -import android.os.RemoteException; +import android.util.Log; /** * Implementation of a handler for {@link IGnssStatusListener}. */ -abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatusListener> { - protected GnssStatusListenerHelper(Handler handler) { - super(handler, "GnssStatusListenerHelper"); +public abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatusListener> { + private static final String TAG = "GnssStatusListenerHelper"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + protected GnssStatusListenerHelper(Context context, Handler handler) { + super(context, handler, TAG); setSupported(GnssLocationProvider.isSupported()); } @@ -43,33 +47,22 @@ abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatus } public void onStatusChanged(boolean isNavigating) { - Operation operation; if (isNavigating) { - operation = new Operation() { - @Override - public void execute(IGnssStatusListener listener) throws RemoteException { - listener.onGnssStarted(); - } - }; + foreach((IGnssStatusListener listener, int uid, String packageName) -> { + listener.onGnssStarted(); + }); } else { - operation = new Operation() { - @Override - public void execute(IGnssStatusListener listener) throws RemoteException { - listener.onGnssStopped(); - } - }; + foreach((IGnssStatusListener listener, int uid, String packageName) -> { + listener.onGnssStopped(); + }); } - foreach(operation); } public void onFirstFix(final int timeToFirstFix) { - Operation operation = new Operation() { - @Override - public void execute(IGnssStatusListener listener) throws RemoteException { - listener.onFirstFix(timeToFirstFix); - } - }; - foreach(operation); + foreach((IGnssStatusListener listener, int uid, String packageName) -> { + listener.onFirstFix(timeToFirstFix); + } + ); } public void onSvStatusChanged( @@ -79,30 +72,23 @@ abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatus final float[] elevations, final float[] azimuths, final float[] carrierFreqs) { - Operation operation = new Operation() { - @Override - public void execute(IGnssStatusListener listener) throws RemoteException { - listener.onSvStatusChanged( - svCount, - prnWithFlags, - cn0s, - elevations, - azimuths, - carrierFreqs); + foreach((IGnssStatusListener listener, int uid, String packageName) -> { + if (!hasPermission(uid, packageName)) { + logPermissionDisabledEventNotReported(TAG, packageName, "GNSS status"); + return; } - }; - foreach(operation); + listener.onSvStatusChanged(svCount, prnWithFlags, cn0s, elevations, azimuths, + carrierFreqs); + }); } public void onNmeaReceived(final long timestamp, final String nmea) { - Operation operation = new Operation() { - @Override - public void execute(IGnssStatusListener listener) throws RemoteException { - listener.onNmeaReceived(timestamp, nmea); + foreach((IGnssStatusListener listener, int uid, String packageName) -> { + if (!hasPermission(uid, packageName)) { + logPermissionDisabledEventNotReported(TAG, packageName, "NMEA"); + return; } - }; - foreach(operation); + listener.onNmeaReceived(timestamp, nmea); + }); } - - private interface Operation extends ListenerOperation<IGnssStatusListener> {} } diff --git a/services/core/java/com/android/server/location/LocationProviderInterface.java b/services/core/java/com/android/server/location/LocationProviderInterface.java deleted file mode 100644 index 678596445fe3..000000000000 --- a/services/core/java/com/android/server/location/LocationProviderInterface.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2010 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; - -import android.location.LocationProvider; -import android.os.Bundle; -import android.os.WorkSource; - -import com.android.internal.location.ProviderProperties; -import com.android.internal.location.ProviderRequest; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -/** - * Location Manager's interface for location providers. - * @hide - */ -public abstract class LocationProviderInterface { - - /** Get name. */ - public abstract String getName(); - - /** Enable. */ - public abstract void enable(); - - /** Disable. */ - public abstract void disable(); - - /** Is enabled. */ - public abstract boolean isEnabled(); - - /** Set request. */ - public abstract void setRequest(ProviderRequest request, WorkSource source); - - /** dump. */ - public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args); - - /** Get properties. */ - public abstract ProviderProperties getProperties(); - - /** - * Get status. - * - * @deprecated Will be removed in a future release. - */ - @Deprecated - public int getStatus(Bundle extras) { - return LocationProvider.AVAILABLE; - } - - /** - * Get status update time. - * - * @deprecated Will be removed in a future release. - */ - @Deprecated - public long getStatusUpdateTime() { - return 0; - } - - /** Send extra command. */ - public abstract boolean sendExtraCommand(String command, Bundle extras); -} diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java index b40841467946..dfcef70c8248 100644 --- a/services/core/java/com/android/server/location/LocationProviderProxy.java +++ b/services/core/java/com/android/server/location/LocationProviderProxy.java @@ -18,6 +18,7 @@ package com.android.server.location; import android.annotation.Nullable; import android.content.Context; +import android.location.Location; import android.location.LocationProvider; import android.os.Bundle; import android.os.IBinder; @@ -27,6 +28,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.location.ILocationProvider; +import com.android.internal.location.ILocationProviderManager; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; import com.android.internal.os.BackgroundThread; @@ -41,21 +43,35 @@ import java.io.PrintWriter; /** * Proxy for ILocationProvider implementations. */ -public class LocationProviderProxy extends LocationProviderInterface { +public class LocationProviderProxy extends AbstractLocationProvider { + private static final String TAG = "LocationProviderProxy"; private static final boolean D = LocationManagerService.D; - private final ServiceWatcher mServiceWatcher; - - private final String mName; - // used to ensure that updates to mRequest and mWorkSource are atomic private final Object mRequestLock = new Object(); + private final ServiceWatcher mServiceWatcher; - private volatile boolean mEnabled = false; - @Nullable - private volatile ProviderProperties mProperties; + private final ILocationProviderManager.Stub mManager = new ILocationProviderManager.Stub() { + // executed on binder thread + @Override + public void onSetEnabled(boolean enabled) { + LocationProviderProxy.this.setEnabled(enabled); + } + + // executed on binder thread + @Override + public void onSetProperties(ProviderProperties properties) { + LocationProviderProxy.this.setProperties(properties); + } + + // executed on binder thread + @Override + public void onReportLocation(Location location) { + LocationProviderProxy.this.reportLocation(location); + } + }; @GuardedBy("mRequestLock") @Nullable @@ -69,10 +85,10 @@ public class LocationProviderProxy extends LocationProviderInterface { */ @Nullable public static LocationProviderProxy createAndBind( - Context context, String name, String action, + Context context, LocationProviderManager locationProviderManager, String action, int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNamesResId) { - LocationProviderProxy proxy = new LocationProviderProxy(context, name, + LocationProviderProxy proxy = new LocationProviderProxy(context, locationProviderManager, action, overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId); if (proxy.bind()) { @@ -82,21 +98,27 @@ public class LocationProviderProxy extends LocationProviderInterface { } } - private LocationProviderProxy(Context context, String name, + private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager, String action, int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNamesResId) { + super(locationProviderManager, false); mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId, BackgroundThread.getHandler()) { + @Override protected void onBind() { runOnBinder(LocationProviderProxy.this::initializeService); } + + @Override + protected void onUnbind() { + setEnabled(false); + setProperties(null); + } }; - mName = name; - mProperties = null; mRequest = null; mWorkSource = new WorkSource(); } @@ -107,84 +129,27 @@ public class LocationProviderProxy extends LocationProviderInterface { private void initializeService(IBinder binder) { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - if (D) Log.d(TAG, "applying state to connected service"); - - ProviderProperties[] properties = new ProviderProperties[1]; - ProviderRequest request; - WorkSource source; - synchronized (mRequestLock) { - request = mRequest; - source = mWorkSource; - } + if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher); try { - // load properties from provider - properties[0] = service.getProperties(); - if (properties[0] == null) { - Log.e(TAG, mServiceWatcher.getCurrentPackageName() - + " has invalid location provider properties"); - } + service.setLocationProviderManager(mManager); - // apply current state to new service - if (mEnabled) { - service.enable(); - if (request != null) { - service.setRequest(request, source); + synchronized (mRequestLock) { + if (mRequest != null) { + service.setRequest(mRequest, mWorkSource); } } } catch (RemoteException e) { Log.w(TAG, e); } - - mProperties = properties[0]; } + @Nullable public String getConnectedPackageName() { return mServiceWatcher.getCurrentPackageName(); } @Override - public String getName() { - return mName; - } - - @Override - public ProviderProperties getProperties() { - return mProperties; - } - - @Override - public void enable() { - mEnabled = true; - mServiceWatcher.runOnBinder(binder -> { - ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - try { - service.enable(); - } catch (RemoteException e) { - Log.w(TAG, e); - } - }); - } - - @Override - public void disable() { - mEnabled = false; - mServiceWatcher.runOnBinder(binder -> { - ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - try { - service.disable(); - } catch (RemoteException e) { - Log.w(TAG, e); - } - }); - } - - @Override - public boolean isEnabled() { - return mEnabled; - } - - @Override public void setRequest(ProviderRequest request, WorkSource source) { synchronized (mRequestLock) { mRequest = request; @@ -202,60 +167,53 @@ public class LocationProviderProxy extends LocationProviderInterface { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.append("REMOTE SERVICE"); - pw.append(" name=").append(mName); - pw.append(" pkg=").append(mServiceWatcher.getCurrentPackageName()); - pw.append(" version=").append(Integer.toString(mServiceWatcher.getCurrentPackageVersion())); - pw.append('\n'); + pw.println(" service=" + mServiceWatcher); mServiceWatcher.runOnBinder(binder -> { - ILocationProvider service = ILocationProvider.Stub.asInterface(binder); try { - TransferPipe.dumpAsync(service.asBinder(), fd, args); + TransferPipe.dumpAsync(binder, fd, args); } catch (IOException | RemoteException e) { - pw.println("Failed to dump location provider: " + e); + pw.println(" failed to dump location provider: " + e); } }); } @Override public int getStatus(Bundle extras) { - int[] result = new int[]{LocationProvider.TEMPORARILY_UNAVAILABLE}; + int[] status = new int[] {LocationProvider.TEMPORARILY_UNAVAILABLE}; mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); try { - result[0] = service.getStatus(extras); + status[0] = service.getStatus(extras); } catch (RemoteException e) { Log.w(TAG, e); } }); - return result[0]; + return status[0]; } @Override public long getStatusUpdateTime() { - long[] result = new long[]{0L}; + long[] updateTime = new long[] {0L}; mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); try { - result[0] = service.getStatusUpdateTime(); + updateTime[0] = service.getStatusUpdateTime(); } catch (RemoteException e) { Log.w(TAG, e); } }); - return result[0]; + return updateTime[0]; } @Override - public boolean sendExtraCommand(String command, Bundle extras) { - boolean[] result = new boolean[]{false}; + public void sendExtraCommand(String command, Bundle extras) { mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); try { - result[0] = service.sendExtraCommand(command, extras); + service.sendExtraCommand(command, extras); } catch (RemoteException e) { Log.w(TAG, e); } }); - return result[0]; } } diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java index 145aee3a9e6a..bfbebf74e93d 100644 --- a/services/core/java/com/android/server/location/MockProvider.java +++ b/services/core/java/com/android/server/location/MockProvider.java @@ -16,14 +16,11 @@ package com.android.server.location; -import android.location.ILocationManager; +import android.annotation.Nullable; import android.location.Location; import android.location.LocationProvider; import android.os.Bundle; -import android.os.RemoteException; import android.os.WorkSource; -import android.util.Log; -import android.util.PrintWriterPrinter; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -36,61 +33,55 @@ import java.io.PrintWriter; * * {@hide} */ -public class MockProvider extends LocationProviderInterface { - private final String mName; - private final ProviderProperties mProperties; - private final ILocationManager mLocationManager; +public class MockProvider extends AbstractLocationProvider { - private final Location mLocation; - - private boolean mHasLocation; private boolean mEnabled; - - + @Nullable private Location mLocation; private int mStatus; private long mStatusUpdateTime; private Bundle mExtras; - private static final String TAG = "MockProvider"; - - public MockProvider(String name, ILocationManager locationManager, - ProviderProperties properties) { - if (properties == null) throw new NullPointerException("properties is null"); - - mName = name; - mLocationManager = locationManager; - mProperties = properties; - mLocation = new Location(name); + public MockProvider( + LocationProviderManager locationProviderManager, ProviderProperties properties) { + super(locationProviderManager, true); + mEnabled = true; + mLocation = null; mStatus = LocationProvider.AVAILABLE; - mStatusUpdateTime = 0L; + mStatusUpdateTime = 0; mExtras = null; + + setProperties(properties); } - @Override - public String getName() { - return mName; + /** Sets the enabled state of this mock provider. */ + public void setEnabled(boolean enabled) { + mEnabled = enabled; + super.setEnabled(enabled); } - @Override - public ProviderProperties getProperties() { - return mProperties; + /** Sets the location to report for this mock provider. */ + public void setLocation(Location l) { + mLocation = new Location(l); + if (mEnabled) { + reportLocation(l); + } } - @Override - public void disable() { - mEnabled = false; + /** Sets the status for this mock provider. */ + public void setStatus(int status, Bundle extras, long updateTime) { + mStatus = status; + mStatusUpdateTime = updateTime; + mExtras = extras; } @Override - public void enable() { - mEnabled = true; + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(" last location=" + mLocation); } @Override - public boolean isEnabled() { - return mEnabled; - } + public void setRequest(ProviderRequest request, WorkSource source) {} @Override public int getStatus(Bundle extras) { @@ -107,50 +98,6 @@ public class MockProvider extends LocationProviderInterface { return mStatusUpdateTime; } - public void setLocation(Location l) { - mLocation.set(l); - mHasLocation = true; - if (mEnabled) { - try { - mLocationManager.reportLocation(mLocation, false); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling reportLocation"); - } - } - } - - public void clearLocation() { - mHasLocation = false; - } - - /** - * @deprecated Will be removed in a future release. - */ - @Deprecated - public void setStatus(int status, Bundle extras, long updateTime) { - mStatus = status; - mStatusUpdateTime = updateTime; - mExtras = extras; - } - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - dump(pw, ""); - } - - public void dump(PrintWriter pw, String prefix) { - pw.println(prefix + mName); - pw.println(prefix + "mHasLocation=" + mHasLocation); - pw.println(prefix + "mLocation:"); - mLocation.dump(new PrintWriterPrinter(pw), prefix + " "); - pw.println(prefix + "mExtras=" + mExtras); - } - @Override - public void setRequest(ProviderRequest request, WorkSource source) { } - - @Override - public boolean sendExtraCommand(String command, Bundle extras) { - return false; - } + public void sendExtraCommand(String command, Bundle extras) {} } diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java index 99c92149fa1b..70d64b02e4b4 100644 --- a/services/core/java/com/android/server/location/PassiveProvider.java +++ b/services/core/java/com/android/server/location/PassiveProvider.java @@ -17,13 +17,9 @@ package com.android.server.location; import android.location.Criteria; -import android.location.ILocationManager; import android.location.Location; -import android.location.LocationManager; import android.os.Bundle; -import android.os.RemoteException; import android.os.WorkSource; -import android.util.Log; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -38,41 +34,20 @@ import java.io.PrintWriter; * * {@hide} */ -public class PassiveProvider extends LocationProviderInterface { - private static final String TAG = "PassiveProvider"; +public class PassiveProvider extends AbstractLocationProvider { private static final ProviderProperties PROPERTIES = new ProviderProperties( false, false, false, false, false, false, false, Criteria.POWER_LOW, Criteria.ACCURACY_COARSE); - private final ILocationManager mLocationManager; private boolean mReportLocation; - public PassiveProvider(ILocationManager locationManager) { - mLocationManager = locationManager; - } - - @Override - public String getName() { - return LocationManager.PASSIVE_PROVIDER; - } - - @Override - public ProviderProperties getProperties() { - return PROPERTIES; - } + public PassiveProvider(LocationProviderManager locationProviderManager) { + super(locationProviderManager, true); - @Override - public boolean isEnabled() { - return true; - } + mReportLocation = false; - @Override - public void enable() { - } - - @Override - public void disable() { + setProperties(PROPERTIES); } @Override @@ -82,22 +57,15 @@ public class PassiveProvider extends LocationProviderInterface { public void updateLocation(Location location) { if (mReportLocation) { - try { - // pass the location back to the location manager - mLocationManager.reportLocation(location, true); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling reportLocation"); - } + reportLocation(location); } } @Override - public boolean sendExtraCommand(String command, Bundle extras) { - return false; - } + public void sendExtraCommand(String command, Bundle extras) {} @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("mReportLocation=" + mReportLocation); + pw.println(" report location=" + mReportLocation); } } diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java index fcdb9d1a87ca..37d43fc1da69 100644 --- a/services/core/java/com/android/server/location/RemoteListenerHelper.java +++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java @@ -17,6 +17,8 @@ package com.android.server.location; import android.annotation.NonNull; +import android.app.AppOpsManager; +import android.content.Context; import android.os.Handler; import android.os.IBinder; import android.os.IInterface; @@ -46,6 +48,9 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { private final Map<IBinder, LinkedListener> mListenerMap = new HashMap<>(); + protected final Context mContext; + protected final AppOpsManager mAppOps; + private volatile boolean mIsRegistered; // must access only on handler thread, or read-only private boolean mHasIsSupported; @@ -53,10 +58,12 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { private int mLastReportedResult = RESULT_UNKNOWN; - protected RemoteListenerHelper(Handler handler, String name) { + protected RemoteListenerHelper(Context context, Handler handler, String name) { Preconditions.checkNotNull(name); mHandler = handler; mTag = name; + mContext = context; + mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); } // read-only access for a dump() thread assured via volatile @@ -64,10 +71,10 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { return mIsRegistered; } - public boolean addListener(@NonNull TListener listener) { + public boolean addListener(@NonNull TListener listener, int uid, String packageName) { Preconditions.checkNotNull(listener, "Attempted to register a 'null' listener."); IBinder binder = listener.asBinder(); - LinkedListener deathListener = new LinkedListener(listener); + LinkedListener deathListener = new LinkedListener(listener, uid, packageName); synchronized (mListenerMap) { if (mListenerMap.containsKey(binder)) { // listener already added @@ -102,7 +109,7 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { // asynchronously in the future return true; } - post(listener, getHandlerOperation(result)); + post(deathListener, getHandlerOperation(result)); } return true; } @@ -130,7 +137,7 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { protected abstract ListenerOperation<TListener> getHandlerOperation(int result); protected interface ListenerOperation<TListener extends IInterface> { - void execute(TListener listener) throws RemoteException; + void execute(TListener listener, int uid, String packageName) throws RemoteException; } protected void foreach(ListenerOperation<TListener> operation) { @@ -170,15 +177,28 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { } } + protected boolean hasPermission(int uid, String packageName) { + return mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, uid, packageName) + == AppOpsManager.MODE_ALLOWED; + } + + protected void logPermissionDisabledEventNotReported(String tag, String packageName, + String event) { + if (Log.isLoggable(tag, Log.DEBUG)) { + Log.d(tag, "Location permission disabled. Skipping " + event + " reporting for app: " + + packageName); + } + } + private void foreachUnsafe(ListenerOperation<TListener> operation) { for (LinkedListener linkedListener : mListenerMap.values()) { - post(linkedListener.getUnderlyingListener(), operation); + post(linkedListener, operation); } } - private void post(TListener listener, ListenerOperation<TListener> operation) { + private void post(LinkedListener linkedListener, ListenerOperation<TListener> operation) { if (operation != null) { - mHandler.post(new HandlerRunnable(listener, operation)); + mHandler.post(new HandlerRunnable(linkedListener, operation)); } } @@ -193,13 +213,9 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { } if (!mIsRegistered) { // post back a failure - mHandler.post(new Runnable() { - @Override - public void run() { - synchronized (mListenerMap) { - ListenerOperation<TListener> operation = getHandlerOperation(registrationState); - foreachUnsafe(operation); - } + mHandler.post(() -> { + synchronized (mListenerMap) { + foreachUnsafe(getHandlerOperation(registrationState)); } }); } @@ -208,16 +224,14 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { } private void tryUnregister() { - mHandler.post(new Runnable() { - @Override - public void run() { - if (!mIsRegistered) { - return; + mHandler.post(() -> { + if (!mIsRegistered) { + return; + } + unregisterFromService(); + mIsRegistered = false; } - unregisterFromService(); - mIsRegistered = false; - } - }); + ); } private int calculateCurrentResultUnsafe() { @@ -240,14 +254,13 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { private class LinkedListener implements IBinder.DeathRecipient { private final TListener mListener; + private final int mUid; + private final String mPackageName; - public LinkedListener(@NonNull TListener listener) { + LinkedListener(@NonNull TListener listener, int uid, String packageName) { mListener = listener; - } - - @NonNull - public TListener getUnderlyingListener() { - return mListener; + mUid = uid; + mPackageName = packageName; } @Override @@ -258,18 +271,19 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { } private class HandlerRunnable implements Runnable { - private final TListener mListener; + private final LinkedListener mLinkedListener; private final ListenerOperation<TListener> mOperation; - public HandlerRunnable(TListener listener, ListenerOperation<TListener> operation) { - mListener = listener; + HandlerRunnable(LinkedListener linkedListener, ListenerOperation<TListener> operation) { + mLinkedListener = linkedListener; mOperation = operation; } @Override public void run() { try { - mOperation.execute(mListener); + mOperation.execute(mLinkedListener.mListener, mLinkedListener.mUid, + mLinkedListener.mPackageName); } catch (RemoteException e) { Log.v(mTag, "Error in monitored listener.", e); } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java index c4f1f3d7369d..44804350309d 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java @@ -24,8 +24,7 @@ import android.app.AlarmManager.OnAlarmListener; import android.app.admin.DevicePolicyManager; import android.app.trust.IStrongAuthTracker; import android.content.Context; -import android.content.pm.PackageManager; -import android.hardware.fingerprint.FingerprintManager; +import android.hardware.biometrics.BiometricManager; import android.os.Handler; import android.os.Message; import android.os.RemoteCallbackList; @@ -62,7 +61,7 @@ public class LockSettingsStrongAuth { private final Context mContext; private AlarmManager mAlarmManager; - private FingerprintManager mFingerprintManager; + private BiometricManager mBiometricManager; public LockSettingsStrongAuth(Context context) { mContext = context; @@ -71,9 +70,8 @@ public class LockSettingsStrongAuth { } public void systemReady() { - final PackageManager pm = mContext.getPackageManager(); - if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { - mFingerprintManager = mContext.getSystemService(FingerprintManager.class); + if (BiometricManager.hasBiometrics(mContext)) { + mBiometricManager = mContext.getSystemService(BiometricManager.class); } } @@ -187,9 +185,9 @@ public class LockSettingsStrongAuth { } public void reportSuccessfulStrongAuthUnlock(int userId) { - if (mFingerprintManager != null) { - byte[] token = null; /* TODO: pass real auth token once fp HAL supports it */ - mFingerprintManager.resetTimeout(token); + if (mBiometricManager != null) { + byte[] token = null; /* TODO: pass real auth token once HAL supports it */ + mBiometricManager.resetTimeout(token); } final int argNotUsed = 0; diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index d32c299074a9..0e195bcc98e0 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -29,6 +29,7 @@ import android.hardware.weaver.V1_0.WeaverStatus; import android.os.RemoteException; import android.os.UserManager; import android.security.GateKeeper; +import android.security.Scrypt; import android.service.gatekeeper.GateKeeperResponse; import android.service.gatekeeper.IGateKeeperService; import android.util.ArrayMap; @@ -1173,11 +1174,10 @@ public class SyntheticPasswordManager { } protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) { - return nativeScrypt(password.getBytes(), salt, N, r, p, outLen); + return new Scrypt().scrypt(password.getBytes(), salt, N, r, p, outLen); } native long nativeSidFromPasswordHandle(byte[] handle); - native byte[] nativeScrypt(byte[] password, byte[] salt, int N, int r, int p, int outLen); protected static ArrayList<Byte> toByteArrayList(byte[] data) { ArrayList<Byte> result = new ArrayList<Byte>(data.length); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java index 6e08949b634e..26e82704b357 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java @@ -16,13 +16,17 @@ package com.android.server.locksettings.recoverablekeystore.certificate; -import static javax.xml.xpath.XPathConstants.NODESET; - import android.annotation.IntDef; import android.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -40,7 +44,6 @@ import java.security.cert.CertPathBuilderException; import java.security.cert.CertPathValidator; import java.security.cert.CertPathValidatorException; import java.security.cert.CertStore; -import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.CollectionCertStoreParameters; @@ -58,15 +61,6 @@ import java.util.Set; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; /** Utility functions related to parsing and validating public-key certificates. */ public final class CertUtils { @@ -167,50 +161,63 @@ public final class CertUtils { static List<String> getXmlNodeContents(@MustExist int mustExist, Element rootNode, String... nodeTags) throws CertParsingException { - String expression = String.join("/", nodeTags); - - XPath xPath = XPathFactory.newInstance().newXPath(); - NodeList nodeList; - try { - nodeList = (NodeList) xPath.compile(expression).evaluate(rootNode, NODESET); - } catch (XPathExpressionException e) { - throw new CertParsingException(e); + if (nodeTags.length == 0) { + throw new CertParsingException("The tag list must not be empty"); } - switch (mustExist) { - case MUST_EXIST_UNENFORCED: - break; - - case MUST_EXIST_EXACTLY_ONE: - if (nodeList.getLength() != 1) { - throw new CertParsingException( - "The XML file must contain exactly one node with the path " - + expression); - } - break; - - case MUST_EXIST_AT_LEAST_ONE: - if (nodeList.getLength() == 0) { - throw new CertParsingException( - "The XML file must contain at least one node with the path " - + expression); - } - break; - - default: - throw new UnsupportedOperationException( - "This value of MustExist is not supported: " + mustExist); + // Go down through all the intermediate node tags (except the last tag for the leaf nodes). + // Note that this implementation requires that at most one path exists for the given + // intermediate node tags. + Element parent = rootNode; + for (int i = 0; i < nodeTags.length - 1; i++) { + String tag = nodeTags[i]; + List<Element> children = getXmlDirectChildren(parent, tag); + if ((children.size() == 0 && mustExist != MUST_EXIST_UNENFORCED) + || children.size() > 1) { + throw new CertParsingException( + "The XML file must contain exactly one path with the tag " + tag); + } + if (children.size() == 0) { + return new ArrayList<>(); + } + parent = children.get(0); } + // Then collect the contents of the leaf nodes. + List<Element> leafs = getXmlDirectChildren(parent, nodeTags[nodeTags.length - 1]); + if (mustExist == MUST_EXIST_EXACTLY_ONE && leafs.size() != 1) { + throw new CertParsingException( + "The XML file must contain exactly one node with the path " + + String.join("/", nodeTags)); + } + if (mustExist == MUST_EXIST_AT_LEAST_ONE && leafs.size() == 0) { + throw new CertParsingException( + "The XML file must contain at least one node with the path " + + String.join("/", nodeTags)); + } List<String> result = new ArrayList<>(); - for (int i = 0; i < nodeList.getLength(); i++) { - Node node = nodeList.item(i); + for (Element leaf : leafs) { // Remove whitespaces and newlines. - result.add(node.getTextContent().replaceAll("\\s", "")); + result.add(leaf.getTextContent().replaceAll("\\s", "")); } return result; } + /** Get the direct child nodes with a given tag. */ + private static List<Element> getXmlDirectChildren(Element parent, String tag) { + // Cannot use Element.getElementsByTagName because it will return all descendant elements + // with the tag name, i.e. not only the direct child nodes. + List<Element> children = new ArrayList<>(); + NodeList childNodes = parent.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(tag)) { + children.add((Element) node); + } + } + return children; + } + /** * Decodes a base64-encoded string. * diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java index a16dcf358d59..a2e7e0cae96b 100644 --- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java @@ -352,7 +352,7 @@ public class NetworkStatsRecorder { // Clear UID from current stats snapshot if (mLastSnapshot != null) { - mLastSnapshot = mLastSnapshot.withoutUids(uids); + mLastSnapshot.removeUids(uids); } final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 95e1962fdbfb..fc364c01aa4f 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -207,6 +207,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.DeviceIdleController; import com.android.server.EventLogTags; +import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.lights.Light; @@ -265,7 +266,7 @@ public class NotificationManagerService extends SystemService { // message codes static final int MESSAGE_DURATION_REACHED = 2; - static final int MESSAGE_SAVE_POLICY_FILE = 3; + // 3: removed to a different handler static final int MESSAGE_SEND_RANKING_UPDATE = 4; static final int MESSAGE_LISTENER_HINTS_CHANGED = 5; static final int MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED = 6; @@ -573,7 +574,7 @@ public class NotificationManagerService extends SystemService { mListeners.migrateToXml(); mAssistants.migrateToXml(); mConditionProviders.migrateToXml(); - savePolicyFile(); + handleSavePolicyFile(); } mAssistants.ensureAssistant(); @@ -603,34 +604,29 @@ public class NotificationManagerService extends SystemService { } } - /** - * Saves notification policy - */ - public void savePolicyFile() { - mHandler.removeMessages(MESSAGE_SAVE_POLICY_FILE); - mHandler.sendEmptyMessage(MESSAGE_SAVE_POLICY_FILE); - } - - private void handleSavePolicyFile() { - if (DBG) Slog.d(TAG, "handleSavePolicyFile"); - synchronized (mPolicyFile) { - final FileOutputStream stream; - try { - stream = mPolicyFile.startWrite(); - } catch (IOException e) { - Slog.w(TAG, "Failed to save policy file", e); - return; - } + @VisibleForTesting + protected void handleSavePolicyFile() { + IoThread.getHandler().post(() -> { + if (DBG) Slog.d(TAG, "handleSavePolicyFile"); + synchronized (mPolicyFile) { + final FileOutputStream stream; + try { + stream = mPolicyFile.startWrite(); + } catch (IOException e) { + Slog.w(TAG, "Failed to save policy file", e); + return; + } - try { - writePolicyXml(stream, false /*forBackup*/); - mPolicyFile.finishWrite(stream); - } catch (IOException e) { - Slog.w(TAG, "Failed to save policy file, restoring backup", e); - mPolicyFile.failWrite(stream); + try { + writePolicyXml(stream, false /*forBackup*/); + mPolicyFile.finishWrite(stream); + } catch (IOException e) { + Slog.w(TAG, "Failed to save policy file, restoring backup", e); + mPolicyFile.failWrite(stream); + } } - } - BackupManager.dataChanged(getContext().getPackageName()); + BackupManager.dataChanged(getContext().getPackageName()); + }); } private void writePolicyXml(OutputStream stream, boolean forBackup) throws IOException { @@ -1138,8 +1134,9 @@ public class NotificationManagerService extends SystemService { } } + mHandler.scheduleOnPackageChanged(removingPackage, changeUserId, pkgList, uidList); - savePolicyFile(); + handleSavePolicyFile(); } } }; @@ -1206,7 +1203,7 @@ public class NotificationManagerService extends SystemService { mListeners.onUserRemoved(userId); mConditionProviders.onUserRemoved(userId); mAssistants.onUserRemoved(userId); - savePolicyFile(); + handleSavePolicyFile(); } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); mUserProfiles.updateCache(context); @@ -1462,7 +1459,7 @@ public class NotificationManagerService extends SystemService { mZenModeHelper.addCallback(new ZenModeHelper.Callback() { @Override public void onConfigChanged() { - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -1764,7 +1761,7 @@ public class NotificationManagerService extends SystemService { modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); } - savePolicyFile(); + handleSavePolicyFile(); } private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel preUpdate, @@ -2232,7 +2229,7 @@ public class NotificationManagerService extends SystemService { Slog.w(TAG, "Can't notify app about app block change", e); } - savePolicyFile(); + handleSavePolicyFile(); } /** @@ -2289,7 +2286,7 @@ public class NotificationManagerService extends SystemService { public void setShowBadge(String pkg, int uid, boolean showBadge) { checkCallerIsSystem(); mPreferencesHelper.setShowBadge(pkg, uid, showBadge); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2305,7 +2302,7 @@ public class NotificationManagerService extends SystemService { if (info != null) { mPreferencesHelper.setNotificationDelegate( callingPkg, callingUid, delegate, info.uid); - savePolicyFile(); + handleSavePolicyFile(); } } catch (RemoteException e) { // :( @@ -2316,7 +2313,7 @@ public class NotificationManagerService extends SystemService { public void revokeNotificationDelegate(String callingPkg) { checkCallerIsSameApp(callingPkg); mPreferencesHelper.revokeNotificationDelegate(callingPkg, Binder.getCallingUid()); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2351,7 +2348,7 @@ public class NotificationManagerService extends SystemService { NotificationChannelGroup group) throws RemoteException { enforceSystemOrSystemUI("Caller not system or systemui"); createNotificationChannelGroup(pkg, uid, group, false, false); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2364,7 +2361,7 @@ public class NotificationManagerService extends SystemService { final NotificationChannelGroup group = groups.get(i); createNotificationChannelGroup(pkg, Binder.getCallingUid(), group, true, false); } - savePolicyFile(); + handleSavePolicyFile(); } private void createNotificationChannelsImpl(String pkg, int uid, @@ -2382,7 +2379,7 @@ public class NotificationManagerService extends SystemService { mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false), NOTIFICATION_CHANNEL_OR_GROUP_ADDED); } - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2427,7 +2424,7 @@ public class NotificationManagerService extends SystemService { UserHandle.getUserHandleForUid(callingUid), mPreferencesHelper.getNotificationChannel(pkg, callingUid, channelId, true), NOTIFICATION_CHANNEL_OR_GROUP_DELETED); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -2469,7 +2466,7 @@ public class NotificationManagerService extends SystemService { mListeners.notifyNotificationChannelGroupChanged( pkg, UserHandle.getUserHandleForUid(callingUid), groupToDelete, NOTIFICATION_CHANNEL_OR_GROUP_DELETED); - savePolicyFile(); + handleSavePolicyFile(); } } @@ -2602,7 +2599,7 @@ public class NotificationManagerService extends SystemService { true, UserHandle.getCallingUserId(), packages, uids); } - savePolicyFile(); + handleSavePolicyFile(); } @@ -3390,7 +3387,7 @@ public class NotificationManagerService extends SystemService { final ByteArrayInputStream bais = new ByteArrayInputStream(payload); try { readPolicyXml(bais, true /*forRestore*/); - savePolicyFile(); + handleSavePolicyFile(); } catch (NumberFormatException | XmlPullParserException | IOException e) { Slog.w(TAG, "applyRestore: error reading payload", e); } @@ -3431,7 +3428,7 @@ public class NotificationManagerService extends SystemService { .setPackage(pkg) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.of(userId), null); - savePolicyFile(); + handleSavePolicyFile(); } } finally { Binder.restoreCallingIdentity(identity); @@ -3575,7 +3572,7 @@ public class NotificationManagerService extends SystemService { .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.of(userId), null); - savePolicyFile(); + handleSavePolicyFile(); } } finally { Binder.restoreCallingIdentity(identity); @@ -3601,7 +3598,7 @@ public class NotificationManagerService extends SystemService { .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.of(userId), null); - savePolicyFile(); + handleSavePolicyFile(); } } finally { Binder.restoreCallingIdentity(identity); @@ -3624,12 +3621,19 @@ public class NotificationManagerService extends SystemService { && mAssistants.isSameUser(token, r.getUserId())) { applyAdjustment(r, adjustment); r.applyAdjustments(); + // importance is checked at the beginning of the + // PostNotificationRunnable, before the signal extractors are run, so + // calculate the final importance here + r.calculateImportance(); foundEnqueued = true; break; } } if (!foundEnqueued) { // adjustment arrived too late to apply to enqueued; apply to posted + // However, since the notification is now posted and may have alerted, + // ignore any importance related adjustments + adjustment.getSignals().remove(Adjustment.KEY_IMPORTANCE); applyAdjustmentFromAssistant(token, adjustment); } } @@ -3659,8 +3663,12 @@ public class NotificationManagerService extends SystemService { NotificationRecord r = mNotificationsByKey.get(adjustment.getKey()); if (r != null && mAssistants.isSameUser(token, r.getUserId())) { applyAdjustment(r, adjustment); - r.applyImportanceFromAdjustments(); - if (r.getImportance() == IMPORTANCE_NONE) { + // If the assistant has blocked the notification, cancel it + // This will trigger a sort, so we don't have to explicitly ask for + // one here. + if (adjustment.getSignals().containsKey(Adjustment.KEY_IMPORTANCE) + && adjustment.getSignals().getInt(Adjustment.KEY_IMPORTANCE) + == IMPORTANCE_NONE) { cancelNotificationsFromListener(token, new String[]{r.getKey()}); } else { needsSort = true; @@ -3684,7 +3692,7 @@ public class NotificationManagerService extends SystemService { verifyPrivilegedListener(token, user, false); createNotificationChannelGroup( pkg, getUidForPackageAndUser(pkg, user), group, false, true); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -3733,7 +3741,7 @@ public class NotificationManagerService extends SystemService { } if (allow != mLockScreenAllowSecureNotifications) { mLockScreenAllowSecureNotifications = allow; - savePolicyFile(); + handleSavePolicyFile(); } } @@ -4492,7 +4500,7 @@ public class NotificationManagerService extends SystemService { Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey()); } mSnoozeHelper.update(userId, r); - savePolicyFile(); + handleSavePolicyFile(); return false; } @@ -4623,7 +4631,7 @@ public class NotificationManagerService extends SystemService { mSnoozeHelper.snooze(r, mDuration); } r.recordSnoozed(); - savePolicyFile(); + handleSavePolicyFile(); } } @@ -4701,7 +4709,7 @@ public class NotificationManagerService extends SystemService { if (mReason != REASON_SNOOZED) { final boolean wasSnoozed = mSnoozeHelper.cancel(mUserId, mPkg, mTag, mId); if (wasSnoozed) { - savePolicyFile(); + handleSavePolicyFile(); } } } @@ -4761,7 +4769,6 @@ public class NotificationManagerService extends SystemService { enqueueStatus); } - mRankingHelper.extractSignals(r); // tell the assistant service about the notification if (mAssistants.isEnabled()) { mAssistants.onNotificationEnqueuedLocked(r); @@ -4845,7 +4852,7 @@ public class NotificationManagerService extends SystemService { | Notification.FLAG_NO_CLEAR; } - applyZenModeLocked(r); + mRankingHelper.extractSignals(r); mRankingHelper.sort(mNotificationList); if (!r.isHidden()) { @@ -5511,7 +5518,12 @@ public class NotificationManagerService extends SystemService { { mHandler.removeCallbacksAndMessages(r); Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r); - long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; + int delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; + // Accessibility users may need longer timeout duration. This api compares original delay + // with user's preference and return longer one. It returns original delay if there's no + // preference. + delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay, + AccessibilityManager.FLAG_CONTENT_TEXT); mHandler.sendMessageDelayed(m, delay); } @@ -5625,6 +5637,7 @@ public class NotificationManagerService extends SystemService { ArrayList<Integer> suppressVisuallyBefore = new ArrayList<>(N); ArrayList<ArrayList<Notification.Action>> systemSmartActionsBefore = new ArrayList<>(N); ArrayList<ArrayList<CharSequence>> smartRepliesBefore = new ArrayList<>(N); + int[] importancesBefore = new int[N]; for (int i = 0; i < N; i++) { final NotificationRecord r = mNotificationList.get(i); orderBefore.add(r.getKey()); @@ -5638,6 +5651,7 @@ public class NotificationManagerService extends SystemService { suppressVisuallyBefore.add(r.getSuppressedVisualEffects()); systemSmartActionsBefore.add(r.getSystemGeneratedSmartActions()); smartRepliesBefore.add(r.getSmartReplies()); + importancesBefore[i] = r.getImportance(); mRankingHelper.extractSignals(r); } mRankingHelper.sort(mNotificationList); @@ -5655,7 +5669,8 @@ public class NotificationManagerService extends SystemService { r.getSuppressedVisualEffects()) || !Objects.equals(systemSmartActionsBefore.get(i), r.getSystemGeneratedSmartActions()) - || !Objects.equals(smartRepliesBefore.get(i), r.getSmartReplies())) { + || !Objects.equals(smartRepliesBefore.get(i), r.getSmartReplies()) + || importancesBefore[i] != r.getImportance()) { mHandler.scheduleSendRankingUpdate(); return; } @@ -5745,9 +5760,6 @@ public class NotificationManagerService extends SystemService { case MESSAGE_FINISH_TOKEN_TIMEOUT: handleKillTokenTimeout((ToastRecord) msg.obj); break; - case MESSAGE_SAVE_POLICY_FILE: - handleSavePolicyFile(); - break; case MESSAGE_SEND_RANKING_UPDATE: handleSendRankingUpdate(); break; @@ -6265,7 +6277,7 @@ public class NotificationManagerService extends SystemService { Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName)); } mSnoozeHelper.repost(key); - savePolicyFile(); + handleSavePolicyFile(); } @GuardedBy("mNotificationLock") diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 1a9257cf17fc..50810ccc8cbc 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -21,10 +21,8 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; -import static android.service.notification.NotificationListenerService.Ranking - .USER_SENTIMENT_NEUTRAL; -import static android.service.notification.NotificationListenerService.Ranking - .USER_SENTIMENT_POSITIVE; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; import android.annotation.Nullable; import android.app.ActivityManager; @@ -609,6 +607,17 @@ public final class NotificationRecord { mIsAppImportanceLocked, this.sbn.getNotification()); } + public boolean hasAdjustment(String key) { + synchronized (mAdjustments) { + for (Adjustment adjustment : mAdjustments) { + if (adjustment.getSignals().containsKey(key)) { + return true; + } + } + } + return false; + } + public void addAdjustment(Adjustment adjustment) { synchronized (mAdjustments) { mAdjustments.add(adjustment); @@ -669,18 +678,6 @@ public final class NotificationRecord { .addTaggedData(MetricsEvent.ADJUSTMENT_KEY_SMART_REPLIES, getSmartReplies().size())); } - } - applyImportanceFromAdjustments(); - } - } - - /** - * Update importance from the adjustment. - */ - public void applyImportanceFromAdjustments() { - synchronized (mAdjustments) { - for (Adjustment adjustment : mAdjustments) { - Bundle signals = adjustment.getSignals(); if (signals.containsKey(Adjustment.KEY_IMPORTANCE)) { int importance = signals.getInt(Adjustment.KEY_IMPORTANCE); importance = Math.max(IMPORTANCE_UNSPECIFIED, importance); @@ -752,6 +749,8 @@ public final class NotificationRecord { */ public void setSystemImportance(int importance) { mSystemImportance = importance; + // System importance is only changed in enqueue, so it's ok for us to calculate the + // importance directly instead of waiting for signal extractor. calculateImportance(); } @@ -762,7 +761,16 @@ public final class NotificationRecord { */ public void setAssistantImportance(int importance) { mAssistantImportance = importance; - calculateImportance(); + // Unlike the system importance, the assistant importance can change on posted + // notifications, so don't calculateImportance() here, but wait for the signal extractors. + } + + /** + * Returns the importance set by the assistant, or IMPORTANCE_UNSPECIFIED if the assistant + * hasn't set it. + */ + public int getAssistantImportance() { + return mAssistantImportance; } /** @@ -774,7 +782,8 @@ public final class NotificationRecord { if (getChannel().hasUserSetImportance()) { mImportanceExplanation = "user"; } - if (!getChannel().hasUserSetImportance() && mAssistantImportance != IMPORTANCE_UNSPECIFIED) { + if (!getChannel().hasUserSetImportance() + && mAssistantImportance != IMPORTANCE_UNSPECIFIED) { mImportance = mAssistantImportance; mImportanceExplanation = "asst"; } diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index 65ccecdcafff..7ae2271adb19 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -19,6 +19,7 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.AppOpsManager; @@ -77,18 +78,20 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { IApplicationThread caller, String callingPackage, ComponentName component, - UserHandle user) throws RemoteException { + @UserIdInt int userId, + boolean launchMainActivity) throws RemoteException { Preconditions.checkNotNull(callingPackage); Preconditions.checkNotNull(component); - Preconditions.checkNotNull(user); verifyCallingPackage(callingPackage); + final int callerUserId = mInjector.getCallingUserId(); + final int callingUid = mInjector.getCallingUid(); + List<UserHandle> allowedTargetUsers = getTargetUserProfilesUnchecked( - callingPackage, mInjector.getCallingUserId()); - if (!allowedTargetUsers.contains(user)) { - throw new SecurityException( - callingPackage + " cannot access unrelated user " + user.getIdentifier()); + callingPackage, callerUserId); + if (!allowedTargetUsers.contains(UserHandle.of(userId))) { + throw new SecurityException(callingPackage + " cannot access unrelated user " + userId); } // Verify that caller package is starting activity in its own package. @@ -98,25 +101,43 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { + component.getPackageName()); } - final int callingUid = mInjector.getCallingUid(); - - // Verify that target activity does handle the intent with ACTION_MAIN and - // CATEGORY_LAUNCHER as calling startActivityAsUser ignore them if component is present. - final Intent launchIntent = new Intent(Intent.ACTION_MAIN); - launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); - launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - // Only package name is set here, as opposed to component name, because intent action and - // category are ignored if component name is present while we are resolving intent. - launchIntent.setPackage(component.getPackageName()); - verifyActivityCanHandleIntentAndExported(launchIntent, component, callingUid, user); + // Verify that target activity does handle the intent correctly. + final Intent launchIntent = new Intent(); + if (launchMainActivity) { + launchIntent.setAction(Intent.ACTION_MAIN); + launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); + launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + // Only package name is set here, as opposed to component name, because intent action + // and category are ignored if component name is present while we are resolving intent. + launchIntent.setPackage(component.getPackageName()); + } else { + // If the main activity is not being launched and the users are different, the caller + // must have the required permission and the users must be in the same profile group + // in order to launch any of its own activities. + if (callerUserId != userId) { + final int permissionFlag = ActivityManager.checkComponentPermission( + android.Manifest.permission.INTERACT_ACROSS_PROFILES, callingUid, + -1, true); + if (permissionFlag != PackageManager.PERMISSION_GRANTED + || !mInjector.getUserManager().isSameProfileGroup(callerUserId, userId)) { + throw new SecurityException("Attempt to launch activity without required " + + android.Manifest.permission.INTERACT_ACROSS_PROFILES + " permission" + + " or target user is not in the same profile group."); + } + } + launchIntent.setComponent(component); + } + verifyActivityCanHandleIntentAndExported(launchIntent, component, callingUid, userId); launchIntent.setPackage(null); launchIntent.setComponent(component); mInjector.getActivityTaskManagerInternal().startActivityAsUser( caller, callingPackage, launchIntent, - ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(), - user.getIdentifier()); + launchMainActivity + ? ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle() + : null, + userId); } private List<UserHandle> getTargetUserProfilesUnchecked( @@ -163,7 +184,7 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { * activity is exported. */ private void verifyActivityCanHandleIntentAndExported( - Intent launchIntent, ComponentName component, int callingUid, UserHandle user) { + Intent launchIntent, ComponentName component, int callingUid, @UserIdInt int userId) { final long ident = mInjector.clearCallingIdentity(); try { final List<ResolveInfo> apps = @@ -171,7 +192,7 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { launchIntent, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, callingUid, - user.getIdentifier()); + userId); final int size = apps.size(); for (int i = 0; i < size; ++i) { final ActivityInfo activityInfo = apps.get(i).activityInfo; diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 2e9a71a178fb..bbec6a0055a6 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -164,6 +164,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements @GuardedBy("mSessions") private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>(); + // STOPSHIP: This is a temporary mock implementation of staged sessions. This variable + // shouldn't be needed at all. + @GuardedBy("mStagedSessions") + private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>(); + /** Historical sessions kept around for debugging purposes */ @GuardedBy("mSessions") private final List<String> mHistoricalSessions = new ArrayList<>(); @@ -481,10 +486,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements if (!PackageHelper.fitsOnInternal(mContext, params)) { throw new IOException("No suitable internal storage available"); } - } else { + } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) { // For now, installs to adopted media are treated as internal from // an install flag point-of-view. params.installFlags |= PackageManager.INSTALL_INTERNAL; + } else { + params.installFlags |= PackageManager.INSTALL_INTERNAL; // Resolve best location for install, based on combination of // requested install flags, delta size, and manifest settings. @@ -536,6 +543,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements synchronized (mSessions) { mSessions.put(sessionId, session); } + if (params.isStaged) { + synchronized (mStagedSessions) { + mStagedSessions.put(sessionId, session); + } + } mCallbacks.notifySessionCreated(session.sessionId, session.userId); writeSessionsAsync(); @@ -666,6 +678,18 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } @Override + public ParceledListSlice<SessionInfo> getStagedSessions() { + final List<SessionInfo> result = new ArrayList<>(); + synchronized (mStagedSessions) { + for (int i = 0; i < mStagedSessions.size(); i++) { + final PackageInstallerSession session = mStagedSessions.valueAt(i); + result.add(session.generateInfo(false)); + } + } + return new ParceledListSlice<>(result); + } + + @Override public ParceledListSlice<SessionInfo> getAllSessions(int userId) { mPermissionManager.enforceCrossUserPermission( Binder.getCallingUid(), userId, true, false, "getAllSessions"); @@ -1110,6 +1134,18 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mInstallHandler.post(new Runnable() { @Override public void run() { + // TODO: remove this mock implementation. + if (session.isStaged()) { + // If the session is aborted, don't keep it in memory. Only store + // sessions successfully staged. + if (!success) { + synchronized (mStagedSessions) { + mStagedSessions.remove(session.sessionId); + } + } else { + return; + } + } synchronized (mSessions) { mSessions.remove(session.sessionId); addHistoricalSessionLocked(session); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index ea190a7301fc..26a92a4cdde4 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -125,7 +125,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class PackageInstallerSession extends IPackageInstallerSession.Stub { - private static final String TAG = "PackageInstaller"; + private static final String TAG = "PackageInstallerSession"; private static final boolean LOGD = true; private static final String REMOVE_SPLIT_MARKER_EXTENSION = ".removed"; @@ -147,6 +147,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_SEALED = "sealed"; private static final String ATTR_MULTI_PACKAGE = "multiPackage"; private static final String ATTR_PARENT_SESSION_ID = "parentSessionId"; + private static final String ATTR_STAGED_SESSION = "stagedSession"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; private static final String ATTR_INSTALL_LOCATION = "installLocation"; @@ -463,6 +464,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.grantedRuntimePermissions = params.grantedRuntimePermissions; info.installFlags = params.installFlags; info.isMultiPackage = params.isMultiPackage; + info.isStaged = params.isStaged; info.parentSessionId = mParentSessionId; info.childSessionIds = mChildSessionIds.copyKeys(); if (info.childSessionIds == null) { @@ -1047,6 +1049,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (committingSession == null) { return; } + if (isStaged()) { + // STOPSHIP: implement staged sessions + dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "Session staged", null); + return; + } if (isMultiPackage()) { final int[] childSessionIds = getChildSessionIds(); List<PackageManagerService.ActiveInstallSession> childSessions = @@ -1816,6 +1823,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @Override + public boolean isStaged() { + return params.isStaged; + } + + @Override public int[] getChildSessionIds() { final int[] childSessionIds = mChildSessionIds.copyKeys(); if (childSessionIds != null) { @@ -1838,6 +1850,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return; } session.setParentSessionId(this.sessionId); + // TODO: sanity check, if parent session is staged then child session should be + // marked as staged. addChildSessionIdInternal(sessionId); } } @@ -1975,6 +1989,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.printPair("mFinalStatus", mFinalStatus); pw.printPair("mFinalMessage", mFinalMessage); pw.printPair("params.isMultiPackage", params.isMultiPackage); + pw.printPair("params.isStaged", params.isStaged); pw.println(); pw.decreaseIndent(); @@ -2026,6 +2041,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { writeBooleanAttribute(out, ATTR_SEALED, isSealed()); writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage); + writeBooleanAttribute(out, ATTR_STAGED_SESSION, params.isStaged); // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after // we've read all sessions. writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId); @@ -2140,6 +2156,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final SessionParams params = new SessionParams( SessionParams.MODE_INVALID); params.isMultiPackage = readBooleanAttribute(in, ATTR_MULTI_PACKAGE, false); + params.isStaged = readBooleanAttribute(in, ATTR_STAGED_SESSION, false); params.mode = readIntAttribute(in, ATTR_MODE); params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS); params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index dab4e79e141c..3e9100e74d8c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -86,11 +86,6 @@ import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.PackageParser.isApkFile; -import static android.content.pm.SharedLibraryNames.ANDROID_HIDL_BASE; -import static android.content.pm.SharedLibraryNames.ANDROID_HIDL_MANAGER; -import static android.content.pm.SharedLibraryNames.ANDROID_TEST_BASE; -import static android.content.pm.SharedLibraryNames.ANDROID_TEST_MOCK; -import static android.content.pm.SharedLibraryNames.ANDROID_TEST_RUNNER; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; import static android.os.storage.StorageManager.FLAG_STORAGE_DE; @@ -242,6 +237,7 @@ import android.os.storage.StorageManager; import android.os.storage.StorageManagerInternal; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; +import android.provider.MediaStore; import android.provider.Settings.Global; import android.provider.Settings.Secure; import android.security.KeyStore; @@ -1301,6 +1297,8 @@ public class PackageManagerService extends IPackageManager.Stub final @Nullable String mSetupWizardPackage; final @Nullable String mStorageManagerPackage; final @Nullable String mSystemTextClassifierPackage; + final @Nullable String mWellbeingPackage; + final @Nullable String mDocumenterPackage; final @NonNull String mServicesSystemSharedLibraryPackageName; final @NonNull String mSharedSystemSharedLibraryPackageName; @@ -2093,28 +2091,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - @GuardedBy("mPackages") - private void setupBuiltinSharedLibraryDependenciesLocked() { - // Builtin libraries don't have versions. - long version = SharedLibraryInfo.VERSION_UNDEFINED; - - SharedLibraryInfo libraryInfo = getSharedLibraryInfoLPr(ANDROID_HIDL_MANAGER, version); - if (libraryInfo != null) { - libraryInfo.addDependency(getSharedLibraryInfoLPr(ANDROID_HIDL_BASE, version)); - } - - libraryInfo = getSharedLibraryInfoLPr(ANDROID_TEST_RUNNER, version); - if (libraryInfo != null) { - libraryInfo.addDependency(getSharedLibraryInfoLPr(ANDROID_TEST_MOCK, version)); - libraryInfo.addDependency(getSharedLibraryInfoLPr(ANDROID_TEST_BASE, version)); - } - - libraryInfo = getSharedLibraryInfoLPr(ANDROID_TEST_MOCK, version); - if (libraryInfo != null) { - libraryInfo.addDependency(getSharedLibraryInfoLPr(ANDROID_TEST_BASE, version)); - } - } - public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES); @@ -2222,17 +2198,32 @@ public class PackageManagerService extends IPackageManager.Stub Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT); mInstantAppRegistry = new InstantAppRegistry(this); - ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries(); + ArrayMap<String, SystemConfig.SharedLibraryEntry> libConfig + = systemConfig.getSharedLibraries(); final int builtInLibCount = libConfig.size(); for (int i = 0; i < builtInLibCount; i++) { String name = libConfig.keyAt(i); - String path = libConfig.valueAt(i); - addSharedLibraryLPw(path, null, null, name, SharedLibraryInfo.VERSION_UNDEFINED, - SharedLibraryInfo.TYPE_BUILTIN, PLATFORM_PACKAGE_NAME, 0); + SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i); + addSharedLibraryLPw(entry.filename, null, null, name, + SharedLibraryInfo.VERSION_UNDEFINED, SharedLibraryInfo.TYPE_BUILTIN, + PLATFORM_PACKAGE_NAME, 0); + } + + // Now that we have added all the libraries, iterate again to add dependency + // information IFF their dependencies are added. + long undefinedVersion = SharedLibraryInfo.VERSION_UNDEFINED; + for (int i = 0; i < builtInLibCount; i++) { + String name = libConfig.keyAt(i); + SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i); + final int dependencyCount = entry.dependencies.length; + for (int j = 0; j < dependencyCount; j++) { + final SharedLibraryInfo dependency = + getSharedLibraryInfoLPr(entry.dependencies[j], undefinedVersion); + if (dependency != null) { + getSharedLibraryInfoLPr(name, undefinedVersion).addDependency(dependency); + } + } } - // Builtin libraries cannot encode their dependency where they are - // defined, so fix that now. - setupBuiltinSharedLibraryDependenciesLocked(); SELinuxMMAC.readInstallPolicy(); @@ -2790,6 +2781,9 @@ public class PackageManagerService extends IPackageManager.Stub mSystemTextClassifierPackage = getSystemTextClassifierPackageName(); + mWellbeingPackage = getWellbeingPackageName(); + mDocumenterPackage = getDocumenterPackageName(); + // Now that we know all of the shared libraries, update all clients to have // the correct library paths. updateAllSharedLibrariesLPw(null); @@ -8631,16 +8625,8 @@ public class PackageManagerService extends IPackageManager.Stub if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) { // The version of the application on the /system partition is less than or - // equal to the version on the /data partition. Even though the disabled system package - // is likely to be replaced by a version on the /data partition, we make assumptions - // that it's part of the mPackages collection during package manager initialization. So, - // add it to mPackages if there isn't already a package in the collection and then throw - // an exception to use the application already installed on the /data partition. - synchronized (mPackages) { - if (!mPackages.containsKey(pkg.packageName)) { - mPackages.put(pkg.packageName, pkg); - } - } + // equal to the version on the /data partition. Throw an exception and use + // the application already installed on the /data partition. throw new PackageManagerException(Log.WARN, "Package " + pkg.packageName + " at " + pkg.codePath + " ignored: updated version " + pkgSetting.versionCode + " better than this " + pkg.getLongVersionCode()); @@ -11266,7 +11252,7 @@ public class PackageManagerService extends IPackageManager.Stub == UsesPermissionInfo.USAGE_UNDEFINED || upi.getDataRetention() == UsesPermissionInfo.RETENTION_UNDEFINED) { // STOPSHIP: Make this throw - Slog.wtf(TAG, "Package " + pkg.packageName + " does not provide usage " + Slog.e(TAG, "Package " + pkg.packageName + " does not provide usage " + "information for permission " + upi.getPermission() + ". This will be a fatal error in Q."); } @@ -12844,16 +12830,17 @@ public class PackageManagerService extends IPackageManager.Stub final List<String> unactionedPackages = new ArrayList<>(packageNames.length); final long callingId = Binder.clearCallingIdentity(); try { - synchronized (mPackages) { - for (int i = 0; i < packageNames.length; i++) { - final String packageName = packageNames[i]; - if (callingPackage.equals(packageName)) { - Slog.w(TAG, "Calling package: " + callingPackage + " trying to " - + (suspended ? "" : "un") + "suspend itself. Ignoring"); - unactionedPackages.add(packageName); - continue; - } - final PackageSetting pkgSetting = mSettings.mPackages.get(packageName); + for (int i = 0; i < packageNames.length; i++) { + final String packageName = packageNames[i]; + if (callingPackage.equals(packageName)) { + Slog.w(TAG, "Calling package: " + callingPackage + " trying to " + + (suspended ? "" : "un") + "suspend itself. Ignoring"); + unactionedPackages.add(packageName); + continue; + } + PackageSetting pkgSetting; + synchronized (mPackages) { + pkgSetting = mSettings.mPackages.get(packageName); if (pkgSetting == null || filterAppAccessLPr(pkgSetting, callingUid, userId)) { Slog.w(TAG, "Could not find package setting for package: " + packageName @@ -12861,15 +12848,20 @@ public class PackageManagerService extends IPackageManager.Stub unactionedPackages.add(packageName); continue; } - if (suspended && !canSuspendPackageForUserLocked(packageName, userId)) { - unactionedPackages.add(packageName); - continue; + } + if (suspended && !canSuspendPackageForUserInternal(packageName, userId)) { + unactionedPackages.add(packageName); + continue; + } + synchronized (mPackages) { + pkgSetting = mSettings.mPackages.get(packageName); + if (pkgSetting != null) { + pkgSetting.setSuspended(suspended, callingPackage, dialogInfo, appExtras, + launcherExtras, userId); } - pkgSetting.setSuspended(suspended, callingPackage, dialogInfo, appExtras, - launcherExtras, userId); - changedPackagesList.add(packageName); - changedUids.add(UserHandle.getUid(userId, pkgSetting.appId)); } + changedPackagesList.add(packageName); + changedUids.add(UserHandle.getUid(userId, pkgSetting.appId)); } } finally { Binder.restoreCallingIdentity(callingId); @@ -13026,16 +13018,13 @@ public class PackageManagerService extends IPackageManager.Stub } final long identity = Binder.clearCallingIdentity(); try { - synchronized (mPackages) { - return canSuspendPackageForUserLocked(packageName, userId); - } + return canSuspendPackageForUserInternal(packageName, userId); } finally { Binder.restoreCallingIdentity(identity); } } - @GuardedBy("mPackages") - private boolean canSuspendPackageForUserLocked(String packageName, int userId) { + private boolean canSuspendPackageForUserInternal(String packageName, int userId) { if (isPackageDeviceAdmin(packageName, userId)) { Slog.w(TAG, "Cannot suspend package \"" + packageName + "\": has an active device admin"); @@ -13079,21 +13068,23 @@ public class PackageManagerService extends IPackageManager.Stub return false; } - if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { - Slog.w(TAG, "Cannot suspend package \"" + packageName - + "\": protected package"); - return false; - } + synchronized (mPackages) { + if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { + Slog.w(TAG, "Cannot suspend package \"" + packageName + + "\": protected package"); + return false; + } - // Cannot suspend static shared libs as they are considered - // a part of the using app (emulating static linking). Also - // static libs are installed always on internal storage. - PackageParser.Package pkg = mPackages.get(packageName); - if (pkg != null && pkg.applicationInfo.isStaticSharedLibrary()) { - Slog.w(TAG, "Cannot suspend package: " + packageName - + " providing static shared library: " - + pkg.staticSharedLibName); - return false; + // Cannot suspend static shared libs as they are considered + // a part of the using app (emulating static linking). Also + // static libs are installed always on internal storage. + PackageParser.Package pkg = mPackages.get(packageName); + if (pkg != null && pkg.applicationInfo.isStaticSharedLibrary()) { + Slog.w(TAG, "Cannot suspend package: " + packageName + + " providing static shared library: " + + pkg.staticSharedLibName); + return false; + } } if (PLATFORM_PACKAGE_NAME.equals(packageName)) { @@ -15278,9 +15269,12 @@ public class PackageManagerService extends IPackageManager.Stub final DeletePackageAction deletePackageAction; // we only want to try to delete for non system apps if (prepareResult.replace && !prepareResult.system) { + final boolean killApp = (scanResult.request.scanFlags & SCAN_DONT_KILL_APP) == 0; + final int deleteFlags = PackageManager.DELETE_KEEP_DATA + | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP); deletePackageAction = mayDeletePackageLocked(res.removedInfo, prepareResult.originalPs, prepareResult.disabledPs, - prepareResult.childPackageSettings); + prepareResult.childPackageSettings, deleteFlags, installArgs.user); if (deletePackageAction == null) { throw new ReconcileFailure( PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE, @@ -15362,12 +15356,9 @@ public class PackageManagerService extends IPackageManager.Stub } } } else { - final boolean killApp = (scanRequest.scanFlags & SCAN_DONT_KILL_APP) == 0; - final int deleteFlags = PackageManager.DELETE_KEEP_DATA - | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP); try { executeDeletePackageLIF(reconciledPkg.deletePackageAction, packageName, - null, true, request.mAllUsers, deleteFlags, true, pkg); + true, request.mAllUsers, true, pkg); } catch (SystemDeleteException e) { if (Build.IS_ENG) { throw new RuntimeException("Unexpected failure", e); @@ -17827,12 +17818,23 @@ public class PackageManagerService extends IPackageManager.Stub public final PackageSetting deletingPs; public final PackageSetting disabledPs; public final PackageRemovedInfo outInfo; + public final int flags; + public final UserHandle user; + /** + * True if this package is an unupdated system app that may be deleted by the system. + * When true, disabledPs will be null. + */ + public final boolean mayDeleteUnupdatedSystemApp; private DeletePackageAction(PackageSetting deletingPs, PackageSetting disabledPs, - PackageRemovedInfo outInfo) { + PackageRemovedInfo outInfo, int flags, UserHandle user, + boolean mayDeleteUnupdatedSystemApp) { this.deletingPs = deletingPs; this.disabledPs = disabledPs; this.outInfo = outInfo; + this.flags = flags; + this.user = user; + this.mayDeleteUnupdatedSystemApp = mayDeleteUnupdatedSystemApp; } } @@ -17844,23 +17846,26 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mPackages") private static DeletePackageAction mayDeletePackageLocked( PackageRemovedInfo outInfo, PackageSetting ps, @Nullable PackageSetting disabledPs, - @Nullable PackageSetting[] children) { + @Nullable PackageSetting[] children, int flags, UserHandle user) { if (ps == null) { return null; } + boolean mayDeleteUnupdatedSystemApp = false; if (isSystemApp(ps)) { if (ps.parentPackageName != null) { Slog.w(TAG, "Attempt to delete child system package " + ps.pkg.packageName); return null; } - // Confirm if the system package has been updated - // An updated system app can be deleted. This will also have to restore - // the system pkg from system partition - // reader - if (disabledPs == null) { - Slog.w(TAG, - "Attempt to delete unknown system package " + ps.pkg.packageName); + if (((flags & PackageManager.DELETE_SYSTEM_APP) != 0) && user != null + && user.getIdentifier() != UserHandle.USER_ALL) { + mayDeleteUnupdatedSystemApp = true; + } else if (disabledPs == null) { + // Confirmed if the system package has been updated + // An updated system app can be deleted. This will also have to restore + // the system pkg from system partition + // reader + Slog.w(TAG, "Attempt to delete unknown system package " + ps.pkg.packageName); return null; } } @@ -17877,7 +17882,8 @@ public class PackageManagerService extends IPackageManager.Stub } } } - return new DeletePackageAction(ps, disabledPs, outInfo); + return new DeletePackageAction(ps, disabledPs, outInfo, flags, user, + mayDeleteUnupdatedSystemApp); } /* @@ -17892,7 +17898,7 @@ public class PackageManagerService extends IPackageManager.Stub final PackageSetting ps = mSettings.mPackages.get(packageName); final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps); PackageSetting[] children = mSettings.getChildSettingsLPr(ps); - action = mayDeletePackageLocked(outInfo, ps, disabledPs, children); + action = mayDeletePackageLocked(outInfo, ps, disabledPs, children, flags, user); } if (null == action) { return false; @@ -17901,8 +17907,8 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user); try { - executeDeletePackageLIF(action, packageName, user, deleteCodeAndResources, - allUserHandles, flags, writeSettings, replacingPackage); + executeDeletePackageLIF(action, packageName, deleteCodeAndResources, + allUserHandles, writeSettings, replacingPackage); } catch (SystemDeleteException e) { return false; } @@ -17919,11 +17925,13 @@ public class PackageManagerService extends IPackageManager.Stub /** Deletes a package. Only throws when install of a disabled package fails. */ private void executeDeletePackageLIF(DeletePackageAction action, - String packageName, UserHandle user, boolean deleteCodeAndResources, - int[] allUserHandles, int flags, boolean writeSettings, + String packageName, boolean deleteCodeAndResources, + int[] allUserHandles, boolean writeSettings, PackageParser.Package replacingPackage) throws SystemDeleteException { final PackageSetting ps = action.deletingPs; final PackageRemovedInfo outInfo = action.outInfo; + final UserHandle user = action.user; + final int flags = action.flags; final boolean systemApp = isSystemApp(ps); synchronized (mPackages) { @@ -17936,7 +17944,7 @@ public class PackageManagerService extends IPackageManager.Stub final int removedUserId = (user != null) ? user.getIdentifier() : UserHandle.USER_ALL; - clearPackageStateForUserLIF(ps, removedUserId, outInfo); + clearPackageStateForUserLIF(ps, removedUserId, outInfo, flags); markPackageUninstalledForUserLPw(ps, user); scheduleWritePackageRestrictionsLocked(user); return; @@ -17949,8 +17957,7 @@ public class PackageManagerService extends IPackageManager.Stub } - if (((!systemApp || (flags & PackageManager.DELETE_SYSTEM_APP) != 0) && user != null - && user.getIdentifier() != UserHandle.USER_ALL)) { + if (!systemApp || action.mayDeleteUnupdatedSystemApp) { // The caller is asking that the package only be deleted for a single // user. To do this, we just mark its uninstalled state and delete // its data. If this is a system app, we only allow this to happen if @@ -17966,7 +17973,7 @@ public class PackageManagerService extends IPackageManager.Stub // we need to do is clear this user's data and save that // it is uninstalled. if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users"); - clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo); + clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo, flags); scheduleWritePackageRestrictionsLocked(user); return; } else { @@ -17982,7 +17989,7 @@ public class PackageManagerService extends IPackageManager.Stub // we need to do is clear this user's data and save that // it is uninstalled. if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app"); - clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo); + clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo, flags); scheduleWritePackageRestrictionsLocked(user); return; } @@ -18099,7 +18106,7 @@ public class PackageManagerService extends IPackageManager.Stub } private void clearPackageStateForUserLIF(PackageSetting ps, int userId, - PackageRemovedInfo outInfo) { + PackageRemovedInfo outInfo, int flags) { final PackageParser.Package pkg; synchronized (mPackages) { pkg = mPackages.get(ps.name); @@ -18124,6 +18131,14 @@ public class PackageManagerService extends IPackageManager.Stub } resetUserChangesToRuntimePermissionsAndFlagsLPw(ps, nextUserId); } + // Also delete contributed media, when requested + if ((flags & PackageManager.DELETE_CONTRIBUTED_MEDIA) != 0) { + try { + MediaStore.deleteContributedMedia(mContext, ps.name, UserHandle.of(nextUserId)); + } catch (IOException e) { + Slog.w(TAG, "Failed to delete contributed media for " + ps.name, e); + } + } } if (outInfo != null) { @@ -18310,6 +18325,10 @@ public class PackageManagerService extends IPackageManager.Stub continue; } + if (bp.isRemoved()) { + continue; + } + // If shared user we just reset the state to which only this app contributed. if (ps.sharedUser != null) { boolean used = false; @@ -19566,6 +19585,27 @@ public class PackageManagerService extends IPackageManager.Stub return mContext.getString(R.string.config_defaultTextClassifierPackage); } + private @Nullable String getDocumenterPackageName() { + final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + + final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null, + MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE + | MATCH_DISABLED_COMPONENTS, + UserHandle.myUserId()); + if (matches.size() == 1) { + return matches.get(0).getComponentInfo().packageName; + } else { + Slog.e(TAG, "There should probably be exactly one documenter; found " + + matches.size() + ": matches=" + matches); + return null; + } + } + + @Override + public String getWellbeingPackageName() { + return mContext.getString(R.string.config_defaultWellbeingPackage); + } + @Override public void setApplicationEnabledSetting(String appPackageName, int newState, int flags, int userId, String callingPackage) { @@ -22722,6 +22762,10 @@ public class PackageManagerService extends IPackageManager.Stub return mSystemTextClassifierPackage; case PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER: return mRequiredPermissionControllerPackage; + case PackageManagerInternal.PACKAGE_WELLBEING: + return mWellbeingPackage; + case PackageManagerInternal.PACKAGE_DOCUMENTER: + return mDocumenterPackage; } return null; } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 77f8c3a0308a..37a35a25ac1e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -536,6 +536,7 @@ class PackageManagerShellCommand extends ShellCommand { boolean listInstaller = false; boolean showUid = false; boolean showVersionCode = false; + boolean listApexOnly = false; int uid = -1; int userId = UserHandle.USER_SYSTEM; try { @@ -576,6 +577,10 @@ class PackageManagerShellCommand extends ShellCommand { case "--show-versioncode": showVersionCode = true; break; + case "--apex-only": + getFlags |= PackageManager.MATCH_APEX; + listApexOnly = true; + break; case "--user": userId = UserHandle.parseUserArg(getNextArgRequired()); break; @@ -606,30 +611,34 @@ class PackageManagerShellCommand extends ShellCommand { if (filter != null && !info.packageName.contains(filter)) { continue; } - if (uid != -1 && info.applicationInfo.uid != uid) { + final boolean isApex = info.isApex; + if (uid != -1 && !isApex && info.applicationInfo.uid != uid) { continue; } - final boolean isSystem = + + final boolean isSystem = !isApex && (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0; - if ((!listDisabled || !info.applicationInfo.enabled) && - (!listEnabled || info.applicationInfo.enabled) && + final boolean isEnabled = !isApex && info.applicationInfo.enabled; + if ((!listDisabled || !isEnabled) && + (!listEnabled || isEnabled) && (!listSystem || isSystem) && - (!listThirdParty || !isSystem)) { + (!listThirdParty || !isSystem) && + (!listApexOnly || isApex)) { pw.print("package:"); - if (showSourceDir) { + if (showSourceDir && !isApex) { pw.print(info.applicationInfo.sourceDir); pw.print("="); } pw.print(info.packageName); - if (showVersionCode) { + if (showVersionCode && !isApex) { pw.print(" versionCode:"); pw.print(info.applicationInfo.versionCode); } - if (listInstaller) { + if (listInstaller && !isApex) { pw.print(" installer="); pw.print(mInterface.getInstallerPackageName(info.packageName)); } - if (showUid) { + if (showUid && !isApex) { pw.print(" uid:"); pw.print(info.applicationInfo.uid); } @@ -2284,6 +2293,9 @@ class PackageManagerShellCommand extends ShellCommand { case "--multi-package": sessionParams.setMultiPackage(); break; + case "--staged": + sessionParams.setStaged(); + break; default: throw new IllegalArgumentException("Unknown option " + opt); } @@ -2767,11 +2779,11 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" Prints all system libraries."); pw.println(""); pw.println(" list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] "); - pw.println(" [--uid UID] [--user USER_ID] [FILTER]"); + pw.println(" [--apex-only] [--uid UID] [--user USER_ID] [FILTER]"); pw.println(" Prints all packages; optionally only those whose name contains"); pw.println(" the text in FILTER. Options are:"); pw.println(" -f: see their associated file"); - pw.println(" -a: all known packages"); + pw.println(" -a: all known packages (but excluding APEXes)"); pw.println(" -d: filter to only show disabled packages"); pw.println(" -e: filter to only show enabled packages"); pw.println(" -s: filter to only show system packages"); @@ -2780,6 +2792,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" -l: ignored (used for compatibility with older releases)"); pw.println(" -U: also show the package UID"); pw.println(" -u: also include uninstalled packages"); + pw.println(" --apex-only: only show APEX packages"); pw.println(" --uid UID: filter to only show packages with the given UID"); pw.println(" --user USER_ID: only list packages belonging to the given user"); pw.println(""); @@ -2853,7 +2866,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]"); pw.println(" [--preload] [--instantapp] [--full] [--dont-kill]"); pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [--apex] [-S BYTES]"); - pw.println(" [--multi-package]"); + pw.println(" [--multi-package] [--staged]"); pw.println(" Like \"install\", but starts an install session. Use \"install-write\""); pw.println(" to push data into the session, and \"install-commit\" to finish."); pw.println(""); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index c18ca25d8c1f..d1d5818b8c46 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -31,6 +31,7 @@ import android.app.IActivityManager; import android.app.IStopUserCallback; import android.app.KeyguardManager; import android.app.PendingIntent; +import android.app.admin.DevicePolicyEventLogger; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -73,6 +74,7 @@ import android.os.UserManagerInternal.UserRestrictionsListener; import android.os.storage.StorageManager; import android.security.GateKeeper; import android.service.gatekeeper.IGateKeeperService; +import android.stats.devicepolicy.DevicePolicyEnums; import android.util.AtomicFile; import android.util.IntArray; import android.util.Log; @@ -100,6 +102,8 @@ import com.android.server.am.UserState; import com.android.server.storage.DeviceStorageMonitorInternal; import com.android.server.wm.ActivityTaskManagerInternal; +import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -121,8 +125,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; -import libcore.io.IoUtils; - /** * Service for {@link UserManager}. * @@ -173,6 +175,8 @@ public class UserManagerService extends IUserManager.Stub { private static final String TAG_ENTRY = "entry"; private static final String TAG_VALUE = "value"; private static final String TAG_SEED_ACCOUNT_OPTIONS = "seedAccountOptions"; + private static final String TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL = + "lastRequestQuietModeEnabledCall"; private static final String ATTR_KEY = "key"; private static final String ATTR_VALUE_TYPE = "type"; private static final String ATTR_MULTIPLE = "m"; @@ -268,6 +272,16 @@ public class UserManagerService extends IUserManager.Stub { /** Elapsed realtime since boot when the user was unlocked. */ long unlockRealtime; + private long mLastRequestQuietModeEnabledMillis; + + void setLastRequestQuietModeEnabledMillis(long millis) { + mLastRequestQuietModeEnabledMillis = millis; + } + + long getLastRequestQuietModeEnabledMillis() { + return mLastRequestQuietModeEnabledMillis; + } + void clearSeedAccountData() { seedAccountName = null; seedAccountType = null; @@ -389,8 +403,8 @@ public class UserManagerService extends IUserManager.Stub { final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT); final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL); // Call setQuietModeEnabled on bg thread to avoid ANR - BackgroundThread.getHandler() - .post(() -> setQuietModeEnabled(userHandle, false, target)); + BackgroundThread.getHandler().post(() -> + setQuietModeEnabled(userHandle, false, target, /* callingPackage */ null)); } }; @@ -834,21 +848,24 @@ public class UserManagerService extends IUserManager.Stub { ensureCanModifyQuietMode(callingPackage, Binder.getCallingUid(), target != null); final long identity = Binder.clearCallingIdentity(); try { + boolean result = false; if (enableQuietMode) { - setQuietModeEnabled(userHandle, true /* enableQuietMode */, target); - return true; + setQuietModeEnabled( + userHandle, true /* enableQuietMode */, target, callingPackage); + result = true; } else { boolean needToShowConfirmCredential = mLockPatternUtils.isSecure(userHandle) && !StorageManager.isUserKeyUnlocked(userHandle); if (needToShowConfirmCredential) { showConfirmCredentialToDisableQuietMode(userHandle, target); - return false; } else { - setQuietModeEnabled(userHandle, false /* enableQuietMode */, target); - return true; + setQuietModeEnabled( + userHandle, false /* enableQuietMode */, target, callingPackage); + result = true; } } + return result; } finally { Binder.restoreCallingIdentity(identity); } @@ -894,8 +911,8 @@ public class UserManagerService extends IUserManager.Stub { + "default launcher nor has MANAGE_USERS/MODIFY_QUIET_MODE permission"); } - private void setQuietModeEnabled( - int userHandle, boolean enableQuietMode, IntentSender target) { + private void setQuietModeEnabled(int userHandle, boolean enableQuietMode, + IntentSender target, @Nullable String callingPackage) { final UserInfo profile, parent; final UserData profileUserData; synchronized (mUsersLock) { @@ -927,6 +944,7 @@ public class UserManagerService extends IUserManager.Stub { ActivityManager.getService().startUserInBackgroundWithListener( userHandle, callback); } + logQuietModeEnabled(userHandle, enableQuietMode, callingPackage); } catch (RemoteException e) { // Should not happen, same process. e.rethrowAsRuntimeException(); @@ -935,6 +953,28 @@ public class UserManagerService extends IUserManager.Stub { enableQuietMode); } + private void logQuietModeEnabled(int userHandle, boolean enableQuietMode, + @Nullable String callingPackage) { + UserData userData; + synchronized (mUsersLock) { + userData = getUserDataLU(userHandle); + } + if (userData == null) { + return; + } + final long now = System.currentTimeMillis(); + final long period = (userData.getLastRequestQuietModeEnabledMillis() != 0L + ? now - userData.getLastRequestQuietModeEnabledMillis() + : now - userData.info.creationTime); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.REQUEST_QUIET_MODE_ENABLED) + .setStrings(callingPackage) + .setBoolean(enableQuietMode) + .setTimePeriod(period) + .write(); + userData.setLastRequestQuietModeEnabledMillis(now); + } + @Override public boolean isQuietModeEnabled(int userHandle) { synchronized (mPackagesLock) { @@ -2314,6 +2354,12 @@ public class UserManagerService extends IUserManager.Stub { serializer.endTag(null, TAG_SEED_ACCOUNT_OPTIONS); } + if (userData.getLastRequestQuietModeEnabledMillis() != 0L) { + serializer.startTag(/* namespace */ null, TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL); + serializer.text(String.valueOf(userData.getLastRequestQuietModeEnabledMillis())); + serializer.endTag(/* namespace */ null, TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL); + } + serializer.endTag(null, TAG_USER); serializer.endDocument(); @@ -2408,6 +2454,7 @@ public class UserManagerService extends IUserManager.Stub { String iconPath = null; long creationTime = 0L; long lastLoggedInTime = 0L; + long lastRequestQuietModeEnabledTimestamp = 0L; String lastLoggedInFingerprint = null; int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID; int profileBadge = 0; @@ -2494,6 +2541,11 @@ public class UserManagerService extends IUserManager.Stub { } else if (TAG_SEED_ACCOUNT_OPTIONS.equals(tag)) { seedAccountOptions = PersistableBundle.restoreFromXml(parser); persistSeedData = true; + } else if (TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL.equals(tag)) { + type = parser.next(); + if (type == XmlPullParser.TEXT) { + lastRequestQuietModeEnabledTimestamp = Long.parseLong(parser.getText()); + } } } } @@ -2518,6 +2570,7 @@ public class UserManagerService extends IUserManager.Stub { userData.seedAccountType = seedAccountType; userData.persistSeedData = persistSeedData; userData.seedAccountOptions = seedAccountOptions; + userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp); synchronized (mRestrictionsLock) { if (baseRestrictions != null) { diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 3a74ab51e9c7..36b7269576b6 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -16,6 +16,11 @@ package com.android.server.pm.dex; +import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; +import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; +import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; + import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -26,9 +31,9 @@ import android.database.ContentObserver; import android.os.Build; import android.os.FileUtils; import android.os.RemoteException; -import android.os.storage.StorageManager; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.storage.StorageManager; import android.provider.Settings.Global; import android.util.Log; import android.util.Slog; @@ -48,18 +53,14 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Iterator; -import java.util.List; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; -import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; -import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; -import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; - /** * This class keeps track of how dex files are used. * Every time it gets a notification about a dex file being loaded it tracks @@ -89,6 +90,12 @@ public class DexManager { // encode and save the dex usage data. private final PackageDexUsage mPackageDexUsage; + // PackageDynamicCodeLoading handles recording of dynamic code loading - + // which is similar to PackageDexUsage but records a different aspect of the data. + // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't + // record class loaders or ISAs.) + private final PackageDynamicCodeLoading mPackageDynamicCodeLoading; + private final IPackageManager mPackageManager; private final PackageDexOptimizer mPackageDexOptimizer; private final Object mInstallLock; @@ -126,14 +133,15 @@ public class DexManager { public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo, Installer installer, Object installLock, Listener listener) { - mContext = context; - mPackageCodeLocationsCache = new HashMap<>(); - mPackageDexUsage = new PackageDexUsage(); - mPackageManager = pms; - mPackageDexOptimizer = pdo; - mInstaller = installer; - mInstallLock = installLock; - mListener = listener; + mContext = context; + mPackageCodeLocationsCache = new HashMap<>(); + mPackageDexUsage = new PackageDexUsage(); + mPackageDynamicCodeLoading = new PackageDynamicCodeLoading(); + mPackageManager = pms; + mPackageDexOptimizer = pdo; + mInstaller = installer; + mInstallLock = installLock; + mListener = listener; } public void systemReady() { @@ -207,7 +215,6 @@ public class DexManager { Slog.i(TAG, loadingAppInfo.packageName + " uses unsupported class loader in " + classLoaderNames); } - return; } int dexPathIndex = 0; @@ -236,15 +243,24 @@ public class DexManager { continue; } - // Record dex file usage. If the current usage is a new pattern (e.g. new secondary, - // or UsedByOtherApps), record will return true and we trigger an async write - // to disk to make sure we don't loose the data in case of a reboot. + if (mPackageDynamicCodeLoading.record(searchResult.mOwningPackageName, dexPath, + PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId, + loadingAppInfo.packageName)) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } + + if (classLoaderContexts != null) { - String classLoaderContext = classLoaderContexts[dexPathIndex]; - if (mPackageDexUsage.record(searchResult.mOwningPackageName, - dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, - loadingAppInfo.packageName, classLoaderContext)) { - mPackageDexUsage.maybeWriteAsync(); + // Record dex file usage. If the current usage is a new pattern (e.g. new + // secondary, or UsedByOtherApps), record will return true and we trigger an + // async write to disk to make sure we don't loose the data in case of a reboot. + + String classLoaderContext = classLoaderContexts[dexPathIndex]; + if (mPackageDexUsage.record(searchResult.mOwningPackageName, + dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, + loadingAppInfo.packageName, classLoaderContext)) { + mPackageDexUsage.maybeWriteAsync(); + } } } else { // If we can't find the owner of the dex we simply do not track it. The impact is @@ -268,8 +284,8 @@ public class DexManager { loadInternal(existingPackages); } catch (Exception e) { mPackageDexUsage.clear(); - Slog.w(TAG, "Exception while loading package dex usage. " + - "Starting with a fresh state.", e); + mPackageDynamicCodeLoading.clear(); + Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e); } } @@ -311,15 +327,24 @@ public class DexManager { * all usage information for the package will be removed. */ public void notifyPackageDataDestroyed(String packageName, int userId) { - boolean updated = userId == UserHandle.USER_ALL - ? mPackageDexUsage.removePackage(packageName) - : mPackageDexUsage.removeUserPackage(packageName, userId); // In case there was an update, write the package use info to disk async. - // Note that we do the writing here and not in PackageDexUsage in order to be + // Note that we do the writing here and not in the lower level classes in order to be // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs // multiple updates in PackageDexUsage before writing it). - if (updated) { - mPackageDexUsage.maybeWriteAsync(); + if (userId == UserHandle.USER_ALL) { + if (mPackageDexUsage.removePackage(packageName)) { + mPackageDexUsage.maybeWriteAsync(); + } + if (mPackageDynamicCodeLoading.removePackage(packageName)) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } + } else { + if (mPackageDexUsage.removeUserPackage(packageName, userId)) { + mPackageDexUsage.maybeWriteAsync(); + } + if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } } } @@ -388,8 +413,23 @@ public class DexManager { } } - mPackageDexUsage.read(); - mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); + try { + mPackageDexUsage.read(); + mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); + } catch (Exception e) { + mPackageDexUsage.clear(); + Slog.w(TAG, "Exception while loading package dex usage. " + + "Starting with a fresh state.", e); + } + + try { + mPackageDynamicCodeLoading.read(); + mPackageDynamicCodeLoading.syncData(packageToUsersMap); + } catch (Exception e) { + mPackageDynamicCodeLoading.clear(); + Slog.w(TAG, "Exception while loading package dynamic code usage. " + + "Starting with a fresh state.", e); + } } /** @@ -415,10 +455,16 @@ public class DexManager { * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try * to access the package use. */ + @VisibleForTesting /*package*/ boolean hasInfoOnPackage(String packageName) { return mPackageDexUsage.getPackageUseInfo(packageName) != null; } + @VisibleForTesting + /*package*/ PackageDynamicCode getPackageDynamicCodeInfo(String packageName) { + return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName); + } + /** * Perform dexopt on with the given {@code options} on the secondary dex files. * @return true if all secondary dex files were processed successfully (compiled or skipped @@ -652,7 +698,7 @@ public class DexManager { // to load dex files through it. try { String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath)); - if (dexPathReal != dexPath) { + if (!dexPath.equals(dexPathReal)) { Slog.d(TAG, "Dex loaded with symlink. dexPath=" + dexPath + " dexPathReal=" + dexPathReal); } @@ -675,6 +721,7 @@ public class DexManager { */ public void writePackageDexUsageNow() { mPackageDexUsage.writeNow(); + mPackageDynamicCodeLoading.writeNow(); } private void registerSettingObserver() { diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java new file mode 100644 index 000000000000..f74aa1d69bc8 --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java @@ -0,0 +1,612 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.dex; + +import android.util.AtomicFile; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FastPrintWriter; +import com.android.server.pm.AbstractStatsBase; + +import libcore.io.IoUtils; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Stats file which stores information about secondary code files that are dynamically loaded. + */ +class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { + // Type code to indicate a secondary file containing DEX code. (The char value is how it + // is represented in the text file format.) + static final int FILE_TYPE_DEX = 'D'; + + private static final String TAG = "PackageDynamicCodeLoading"; + + private static final String FILE_VERSION_HEADER = "DCL1"; + private static final String PACKAGE_PREFIX = "P:"; + + private static final char FIELD_SEPARATOR = ':'; + private static final String PACKAGE_SEPARATOR = ","; + + /** + * Regular expression to match the expected format of an input line describing one file. + * <p>Example: {@code D:10:package.name1,package.name2:/escaped/path} + * <p>The capturing groups are the file type, user ID, loading packages and escaped file path + * (in that order). + * <p>See {@link #write(OutputStream, Map)} below for more details of the format. + */ + private static final Pattern PACKAGE_LINE_PATTERN = + Pattern.compile("([A-Z]):([0-9]+):([^:]*):(.*)"); + + private final Object mLock = new Object(); + + // Map from package name to data about loading of dynamic code files owned by that package. + // (Apps may load code files owned by other packages, subject to various access + // constraints.) + // Any PackageDynamicCode in this map will be non-empty. + @GuardedBy("mLock") + private Map<String, PackageDynamicCode> mPackageMap = new HashMap<>(); + + PackageDynamicCodeLoading() { + super("package-dcl.list", "PackageDynamicCodeLoading_DiskWriter", false); + } + + /** + * Record dynamic code loading from a file. + * + * Note this is called when an app loads dex files and as such it should return + * as fast as possible. + * + * @param owningPackageName the package owning the file path + * @param filePath the path of the dex files being loaded + * @param fileType the type of code loading + * @param ownerUserId the user id which runs the code loading the file + * @param loadingPackageName the package performing the load + * @return whether new information has been recorded + * @throws IllegalArgumentException if clearly invalid information is detected + */ + boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId, + String loadingPackageName) { + if (fileType != FILE_TYPE_DEX) { + throw new IllegalArgumentException("Bad file type: " + fileType); + } + synchronized (mLock) { + PackageDynamicCode packageInfo = mPackageMap.get(owningPackageName); + if (packageInfo == null) { + packageInfo = new PackageDynamicCode(); + mPackageMap.put(owningPackageName, packageInfo); + } + return packageInfo.add(filePath, (char) fileType, ownerUserId, loadingPackageName); + } + } + + /** + * Return all packages that contain records of secondary dex files. (Note that data updates + * asynchronously, so {@link #getPackageDynamicCodeInfo} may still return null if passed + * one of these package names.) + */ + Set<String> getAllPackagesWithDynamicCodeLoading() { + synchronized (mLock) { + return new HashSet<>(mPackageMap.keySet()); + } + } + + /** + * Return information about the dynamic code file usage of the specified package, + * or null if there is currently no usage information. The object returned is a copy of the + * live information that is not updated. + */ + PackageDynamicCode getPackageDynamicCodeInfo(String packageName) { + synchronized (mLock) { + PackageDynamicCode info = mPackageMap.get(packageName); + return info == null ? null : new PackageDynamicCode(info); + } + } + + /** + * Remove all information about all packages. + */ + void clear() { + synchronized (mLock) { + mPackageMap.clear(); + } + } + + /** + * Remove the data associated with package {@code packageName}. Affects all users. + * @return true if the package usage was found and removed successfully + */ + boolean removePackage(String packageName) { + synchronized (mLock) { + return mPackageMap.remove(packageName) != null; + } + } + + /** + * Remove all the records about package {@code packageName} belonging to user {@code userId}. + * @return whether any data was actually removed + */ + boolean removeUserPackage(String packageName, int userId) { + synchronized (mLock) { + PackageDynamicCode packageDynamicCode = mPackageMap.get(packageName); + if (packageDynamicCode == null) { + return false; + } + if (packageDynamicCode.removeUser(userId)) { + if (packageDynamicCode.mFileUsageMap.isEmpty()) { + mPackageMap.remove(packageName); + } + return true; + } else { + return false; + } + } + } + + /** + * Remove the specified dynamic code file record belonging to the package {@code packageName} + * and user {@code userId}. + * @return whether data was actually removed + */ + boolean removeFile(String packageName, String filePath, int userId) { + synchronized (mLock) { + PackageDynamicCode packageDynamicCode = mPackageMap.get(packageName); + if (packageDynamicCode == null) { + return false; + } + if (packageDynamicCode.removeFile(filePath, userId)) { + if (packageDynamicCode.mFileUsageMap.isEmpty()) { + mPackageMap.remove(packageName); + } + return true; + } else { + return false; + } + } + } + + /** + * Syncs data with the set of installed packages. Data about packages that are no longer + * installed is removed. + * @param packageToUsersMap a map from all existing package names to the users who have the + * package installed + */ + void syncData(Map<String, Set<Integer>> packageToUsersMap) { + synchronized (mLock) { + Iterator<Entry<String, PackageDynamicCode>> it = mPackageMap.entrySet().iterator(); + while (it.hasNext()) { + Entry<String, PackageDynamicCode> entry = it.next(); + Set<Integer> packageUsers = packageToUsersMap.get(entry.getKey()); + if (packageUsers == null) { + it.remove(); + } else { + PackageDynamicCode packageDynamicCode = entry.getValue(); + packageDynamicCode.syncData(packageToUsersMap, packageUsers); + if (packageDynamicCode.mFileUsageMap.isEmpty()) { + it.remove(); + } + } + } + } + } + + /** + * Request that data be written to persistent file at the next time allowed by write-limiting. + */ + void maybeWriteAsync() { + super.maybeWriteAsync(null); + } + + /** + * Writes data to persistent file immediately. + */ + void writeNow() { + super.writeNow(null); + } + + @Override + protected final void writeInternal(Void data) { + AtomicFile file = getFile(); + FileOutputStream output = null; + try { + output = file.startWrite(); + write(output); + file.finishWrite(output); + } catch (IOException e) { + file.failWrite(output); + Slog.e(TAG, "Failed to write dynamic usage for secondary code files.", e); + } + } + + @VisibleForTesting + void write(OutputStream output) throws IOException { + // Make a deep copy to avoid holding the lock while writing to disk. + Map<String, PackageDynamicCode> copiedMap; + synchronized (mLock) { + copiedMap = new HashMap<>(mPackageMap.size()); + for (Entry<String, PackageDynamicCode> entry : mPackageMap.entrySet()) { + PackageDynamicCode copiedValue = new PackageDynamicCode(entry.getValue()); + copiedMap.put(entry.getKey(), copiedValue); + } + } + + write(output, copiedMap); + } + + /** + * Write the dynamic code loading data as a text file to {@code output}. The file format begins + * with a line indicating the file type and version - {@link #FILE_VERSION_HEADER}. + * <p>There is then one section for each owning package, introduced by a line beginning "P:". + * This is followed by a line for each file owned by the package this is dynamically loaded, + * containing the file type, user ID, loading package names and full path (with newlines and + * backslashes escaped - see {@link #escape}). + * <p>For example: + * <pre>{@code + * DCL1 + * P:first.owning.package + * D:0:loading.package_1,loading.package_2:/path/to/file + * D:10:loading.package_1:/another/file + * P:second.owning.package + * D:0:loading.package:/third/file + * }</pre> + */ + private static void write(OutputStream output, Map<String, PackageDynamicCode> packageMap) + throws IOException { + PrintWriter writer = new FastPrintWriter(output); + + writer.println(FILE_VERSION_HEADER); + for (Entry<String, PackageDynamicCode> packageEntry : packageMap.entrySet()) { + writer.print(PACKAGE_PREFIX); + writer.println(packageEntry.getKey()); + + Map<String, DynamicCodeFile> mFileUsageMap = packageEntry.getValue().mFileUsageMap; + for (Entry<String, DynamicCodeFile> fileEntry : mFileUsageMap.entrySet()) { + String path = fileEntry.getKey(); + DynamicCodeFile dynamicCodeFile = fileEntry.getValue(); + + writer.print(dynamicCodeFile.mFileType); + writer.print(FIELD_SEPARATOR); + writer.print(dynamicCodeFile.mUserId); + writer.print(FIELD_SEPARATOR); + + String prefix = ""; + for (String packageName : dynamicCodeFile.mLoadingPackages) { + writer.print(prefix); + writer.print(packageName); + prefix = PACKAGE_SEPARATOR; + } + + writer.print(FIELD_SEPARATOR); + writer.println(escape(path)); + } + } + + writer.flush(); + if (writer.checkError()) { + throw new IOException("Writer failed"); + } + } + + /** + * Read data from the persistent file. Replaces existing data completely if successful. + */ + void read() { + super.read(null); + } + + @Override + protected final void readInternal(Void data) { + AtomicFile file = getFile(); + + FileInputStream stream = null; + try { + stream = file.openRead(); + read(stream); + } catch (FileNotFoundException expected) { + // The file may not be there. E.g. When we first take the OTA with this feature. + } catch (IOException e) { + Slog.w(TAG, "Failed to parse dynamic usage for secondary code files.", e); + } finally { + IoUtils.closeQuietly(stream); + } + } + + @VisibleForTesting + void read(InputStream stream) throws IOException { + Map<String, PackageDynamicCode> newPackageMap = new HashMap<>(); + read(stream, newPackageMap); + synchronized (mLock) { + mPackageMap = newPackageMap; + } + } + + private static void read(InputStream stream, Map<String, PackageDynamicCode> packageMap) + throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + + String versionLine = reader.readLine(); + if (!FILE_VERSION_HEADER.equals(versionLine)) { + throw new IOException("Incorrect version line: " + versionLine); + } + + String line = reader.readLine(); + if (line != null && !line.startsWith(PACKAGE_PREFIX)) { + throw new IOException("Malformed line: " + line); + } + + while (line != null) { + String packageName = line.substring(PACKAGE_PREFIX.length()); + + PackageDynamicCode packageInfo = new PackageDynamicCode(); + while (true) { + line = reader.readLine(); + if (line == null || line.startsWith(PACKAGE_PREFIX)) { + break; + } + readFileInfo(line, packageInfo); + } + + if (!packageInfo.mFileUsageMap.isEmpty()) { + packageMap.put(packageName, packageInfo); + } + } + } + + private static void readFileInfo(String line, PackageDynamicCode output) throws IOException { + try { + Matcher matcher = PACKAGE_LINE_PATTERN.matcher(line); + if (!matcher.matches()) { + throw new IOException("Malformed line: " + line); + } + + char type = matcher.group(1).charAt(0); + int user = Integer.parseInt(matcher.group(2)); + String[] packages = matcher.group(3).split(PACKAGE_SEPARATOR); + String path = unescape(matcher.group(4)); + + if (packages.length == 0) { + throw new IOException("Malformed line: " + line); + } + if (type != FILE_TYPE_DEX) { + throw new IOException("Unknown file type: " + line); + } + + output.mFileUsageMap.put(path, new DynamicCodeFile(type, user, packages)); + } catch (RuntimeException e) { + // Just in case we get NumberFormatException, or various + // impossible out of bounds errors happen. + throw new IOException("Unable to parse line: " + line, e); + } + } + + /** + * Escape any newline and backslash characters in path. A newline in a path is legal if unusual, + * and it would break our line-based file parsing. + */ + @VisibleForTesting + static String escape(String path) { + if (path.indexOf('\\') == -1 && path.indexOf('\n') == -1 && path.indexOf('\r') == -1) { + return path; + } + + StringBuilder result = new StringBuilder(path.length() + 10); + for (int i = 0; i < path.length(); i++) { + // Surrogates will never match the characters we care about, so it's ok to use chars + // not code points here. + char c = path.charAt(i); + switch (c) { + case '\\': + result.append("\\\\"); + break; + case '\n': + result.append("\\n"); + break; + case '\r': + result.append("\\r"); + break; + default: + result.append(c); + break; + } + } + return result.toString(); + } + + /** + * Reverse the effect of {@link #escape}. + * @throws IOException if the input string is malformed + */ + @VisibleForTesting + static String unescape(String escaped) throws IOException { + // As we move through the input string, start is the position of the first character + // after the previous escape sequence and finish is the position of the following backslash. + int start = 0; + int finish = escaped.indexOf('\\'); + if (finish == -1) { + return escaped; + } + + StringBuilder result = new StringBuilder(escaped.length()); + while (true) { + if (finish >= escaped.length() - 1) { + // Backslash mustn't be the last character + throw new IOException("Unexpected \\ in: " + escaped); + } + result.append(escaped, start, finish); + switch (escaped.charAt(finish + 1)) { + case '\\': + result.append('\\'); + break; + case 'r': + result.append('\r'); + break; + case 'n': + result.append('\n'); + break; + default: + throw new IOException("Bad escape in: " + escaped); + } + + start = finish + 2; + finish = escaped.indexOf('\\', start); + if (finish == -1) { + result.append(escaped, start, escaped.length()); + break; + } + } + return result.toString(); + } + + /** + * Represents the dynamic code usage of a single package. + */ + static class PackageDynamicCode { + /** + * Map from secondary code file path to information about which packages dynamically load + * that file. + */ + final Map<String, DynamicCodeFile> mFileUsageMap; + + private PackageDynamicCode() { + mFileUsageMap = new HashMap<>(); + } + + private PackageDynamicCode(PackageDynamicCode original) { + mFileUsageMap = new HashMap<>(original.mFileUsageMap.size()); + for (Entry<String, DynamicCodeFile> entry : original.mFileUsageMap.entrySet()) { + DynamicCodeFile newValue = new DynamicCodeFile(entry.getValue()); + mFileUsageMap.put(entry.getKey(), newValue); + } + } + + private boolean add(String path, char fileType, int userId, String loadingPackage) { + DynamicCodeFile fileInfo = mFileUsageMap.get(path); + if (fileInfo == null) { + fileInfo = new DynamicCodeFile(fileType, userId, loadingPackage); + mFileUsageMap.put(path, fileInfo); + return true; + } else { + if (fileInfo.mUserId != userId) { + // This should be impossible: private app files are always user-specific and + // can't be accessed from different users. + throw new IllegalArgumentException("Cannot change userId for '" + path + + "' from " + fileInfo.mUserId + " to " + userId); + } + // Changing file type (i.e. loading the same file in different ways is possible if + // unlikely. We allow it but ignore it. + return fileInfo.mLoadingPackages.add(loadingPackage); + } + } + + private boolean removeUser(int userId) { + boolean updated = false; + Iterator<DynamicCodeFile> it = mFileUsageMap.values().iterator(); + while (it.hasNext()) { + DynamicCodeFile fileInfo = it.next(); + if (fileInfo.mUserId == userId) { + it.remove(); + updated = true; + } + } + return updated; + } + + private boolean removeFile(String filePath, int userId) { + DynamicCodeFile fileInfo = mFileUsageMap.get(filePath); + if (fileInfo == null || fileInfo.mUserId != userId) { + return false; + } else { + mFileUsageMap.remove(filePath); + return true; + } + } + + private void syncData(Map<String, Set<Integer>> packageToUsersMap, + Set<Integer> owningPackageUsers) { + Iterator<DynamicCodeFile> fileIt = mFileUsageMap.values().iterator(); + while (fileIt.hasNext()) { + DynamicCodeFile fileInfo = fileIt.next(); + int fileUserId = fileInfo.mUserId; + if (!owningPackageUsers.contains(fileUserId)) { + fileIt.remove(); + } else { + // Also remove information about any loading packages that are no longer + // installed for this user. + Iterator<String> loaderIt = fileInfo.mLoadingPackages.iterator(); + while (loaderIt.hasNext()) { + String loader = loaderIt.next(); + Set<Integer> loadingPackageUsers = packageToUsersMap.get(loader); + if (loadingPackageUsers == null + || !loadingPackageUsers.contains(fileUserId)) { + loaderIt.remove(); + } + } + if (fileInfo.mLoadingPackages.isEmpty()) { + fileIt.remove(); + } + } + } + } + } + + /** + * Represents a single dynamic code file loaded by one or more packages. Note that it is + * possible for one app to dynamically load code from a different app's home dir, if the + * owning app: + * <ul> + * <li>Targets API 27 or lower and has shared its home dir. + * <li>Is a system app. + * <li>Has a shared UID with the loading app. + * </ul> + */ + static class DynamicCodeFile { + final char mFileType; + final int mUserId; + final Set<String> mLoadingPackages; + + private DynamicCodeFile(char type, int user, String... packages) { + mFileType = type; + mUserId = user; + mLoadingPackages = new HashSet<>(Arrays.asList(packages)); + } + + private DynamicCodeFile(DynamicCodeFile original) { + mFileType = original.mFileType; + mUserId = original.mUserId; + mLoadingPackages = new HashSet<>(original.mLoadingPackages); + } + } +} diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java index 2d583ca39adb..3a49412357d8 100644 --- a/services/core/java/com/android/server/pm/permission/BasePermission.java +++ b/services/core/java/com/android/server/pm/permission/BasePermission.java @@ -189,6 +189,12 @@ public final class BasePermission { return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) == PermissionInfo.PROTECTION_DANGEROUS; } + + public boolean isRemoved() { + return perm != null && perm.info != null + && (perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0; + } + public boolean isSignature() { return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) == PermissionInfo.PROTECTION_SIGNATURE; @@ -235,6 +241,12 @@ public final class BasePermission { return (protectionLevel & PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER) != 0; } + public boolean isWellbeing() { + return (protectionLevel & PermissionInfo.PROTECTION_FLAG_WELLBEING) != 0; + } + public boolean isDocumenter() { + return (protectionLevel & PermissionInfo.PROTECTION_FLAG_DOCUMENTER) != 0; + } public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) { if (!origPackageName.equals(sourcePackageName)) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 4406fdde6454..31f5ce47abd9 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -799,6 +799,10 @@ public class PermissionManagerService { continue; } + if (bp.isRemoved()) { + continue; + } + // Limit ephemeral apps to ephemeral allowed permissions. if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) { if (DEBUG_PERMISSIONS) { @@ -1637,6 +1641,19 @@ public class PermissionManagerService { // Special permissions for the system default text classifier. allowed = true; } + if (!allowed && bp.isWellbeing() + && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName( + PackageManagerInternal.PACKAGE_WELLBEING, UserHandle.USER_SYSTEM))) { + // Special permission granted only to the OEM specified wellbeing app + allowed = true; + } + if (!allowed && bp.isDocumenter() + && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName( + PackageManagerInternal.PACKAGE_DOCUMENTER, UserHandle.USER_SYSTEM))) { + // If this permission is to be granted to the documenter and + // this app is the documenter, then it gets the permission. + allowed = true; + } } return allowed; } diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java index c5139b562065..cedb54801dc8 100644 --- a/services/core/java/com/android/server/power/BatterySaverPolicy.java +++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java @@ -111,7 +111,7 @@ public class BatterySaverPolicy extends ContentObserver { false, /* enableAdjustBrightness */ false, /* enableDataSaver */ true, /* enableFirewall */ - false, /* enableQuickDoze */ + true, /* enableQuickDoze */ new ArrayMap<>(), /* filesForInteractive */ new ArrayMap<>(), /* filesForNoninteractive */ true, /* forceAllAppsStandby */ diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 29d62372e154..565bb706a5d8 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -3221,6 +3221,20 @@ public final class PowerManagerService extends SystemService mNativeWrapper.nativeSendPowerHint(hintId, data); } + @VisibleForTesting + boolean wasDeviceIdleForInternal(long ms) { + synchronized (mLock) { + return mLastUserActivityTime + ms < SystemClock.uptimeMillis(); + } + } + + @VisibleForTesting + void onUserActivity() { + synchronized (mLock) { + mLastUserActivityTime = SystemClock.uptimeMillis(); + } + } + /** * Low-level function turn the device off immediately, without trying * to be clean. Most people should use {@link ShutdownThread} for a clean shutdown. @@ -4874,5 +4888,10 @@ public final class PowerManagerService extends SystemService public void powerHint(int hintId, int data) { powerHintInternal(hintId, data); } + + @Override + public boolean wasDeviceIdleFor(long ms) { + return wasDeviceIdleForInternal(ms); + } } } diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java index 07bebad8e190..02689a90a98a 100644 --- a/services/core/java/com/android/server/power/ThermalManagerService.java +++ b/services/core/java/com/android/server/power/ThermalManagerService.java @@ -250,14 +250,29 @@ public class ThermalManagerService extends SystemService { } } - private void onTemperatureChanged(Temperature temperature, boolean sendStatus) { - synchronized (mLock) { - // Thermal Shutdown for Skin temperature - if (temperature.getStatus() == Temperature.THROTTLING_SHUTDOWN - && temperature.getType() == Temperature.TYPE_SKIN) { + private void shutdownIfNeededLocked(Temperature temperature) { + if (temperature.getStatus() != Temperature.THROTTLING_SHUTDOWN) { + return; + } + switch (temperature.getType()) { + case Temperature.TYPE_CPU: + // Fall through + case Temperature.TYPE_GPU: + // Fall through + case Temperature.TYPE_NPU: + // Fall through + case Temperature.TYPE_SKIN: mPowerManager.shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false); - } + break; + case Temperature.TYPE_BATTERY: + mPowerManager.shutdown(false, PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE, false); + break; + } + } + private void onTemperatureChanged(Temperature temperature, boolean sendStatus) { + synchronized (mLock) { + shutdownIfNeededLocked(temperature); Temperature old = mTemperatureMap.put(temperature.getName(), temperature); if (old != null) { if (old.getStatus() != temperature.getStatus()) { @@ -300,6 +315,8 @@ public class ThermalManagerService extends SystemService { final IThermalService.Stub mService = new IThermalService.Stub() { @Override public boolean registerThermalEventListener(IThermalEventListener listener) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, null); synchronized (mLock) { final long token = Binder.clearCallingIdentity(); try { @@ -320,6 +337,8 @@ public class ThermalManagerService extends SystemService { @Override public boolean registerThermalEventListenerWithType(IThermalEventListener listener, int type) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, null); synchronized (mLock) { final long token = Binder.clearCallingIdentity(); try { @@ -339,6 +358,8 @@ public class ThermalManagerService extends SystemService { @Override public boolean unregisterThermalEventListener(IThermalEventListener listener) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, null); synchronized (mLock) { final long token = Binder.clearCallingIdentity(); try { @@ -351,6 +372,8 @@ public class ThermalManagerService extends SystemService { @Override public List<Temperature> getCurrentTemperatures() { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, null); final long token = Binder.clearCallingIdentity(); try { if (!mHalReady) { @@ -364,6 +387,8 @@ public class ThermalManagerService extends SystemService { @Override public List<Temperature> getCurrentTemperaturesWithType(int type) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, null); final long token = Binder.clearCallingIdentity(); try { if (!mHalReady) { @@ -428,14 +453,15 @@ public class ThermalManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { + pw.println("IsStatusOverride: " + mIsStatusOverride); pw.println("ThermalEventListeners:"); mThermalEventListeners.dump(pw, "\t"); pw.println("ThermalStatusListeners:"); mThermalStatusListeners.dump(pw, "\t"); - pw.println("Thermal Status: " + Integer.toString(mStatus)); + pw.println("Thermal Status: " + mStatus); pw.println("Cached temperatures:"); dumpTemperaturesLocked(pw, "\t", mTemperatureMap.values()); - pw.println("HAL Ready: " + Boolean.toString(mHalReady)); + pw.println("HAL Ready: " + mHalReady); if (mHalReady) { pw.println("HAL connection:"); mHalWrapper.dump(pw, "\t"); @@ -507,7 +533,7 @@ public class ThermalManagerService extends SystemService { return -1; } if (!Temperature.isValidStatus(status)) { - pw.println("Invalid status: " + Integer.toString(status)); + pw.println("Invalid status: " + status); return -1; } synchronized (mLock) { diff --git a/services/core/java/com/android/server/role/RemoteRoleControllerService.java b/services/core/java/com/android/server/role/RemoteRoleControllerService.java index cb897801e968..cc3064efae6a 100644 --- a/services/core/java/com/android/server/role/RemoteRoleControllerService.java +++ b/services/core/java/com/android/server/role/RemoteRoleControllerService.java @@ -34,8 +34,8 @@ import android.rolecontrollerservice.IRoleControllerService; import android.rolecontrollerservice.RoleControllerService; import android.util.Slog; -import com.android.internal.os.BackgroundThread; import com.android.internal.util.function.pooled.PooledLambda; +import com.android.server.FgThread; import java.util.ArrayDeque; import java.util.Queue; @@ -48,10 +48,6 @@ public class RemoteRoleControllerService { static final boolean DEBUG = false; private static final String LOG_TAG = RemoteRoleControllerService.class.getSimpleName(); - // TODO: STOPSHIP: This isn't the right thread, as we are also using it to write to disk. - @NonNull - private static final Handler sCallbackHandler = BackgroundThread.getHandler(); - @NonNull private final Connection mConnection; @@ -99,8 +95,8 @@ public class RemoteRoleControllerService { * @see RoleControllerService#onGrantDefaultRoles(RoleManagerCallback) */ public void onGrantDefaultRoles(@NonNull IRoleManagerCallback callback) { - mConnection.enqueueCall( - new Connection.Call(IRoleControllerService::onGrantDefaultRoles, callback)); + mConnection.enqueueCall(new Connection.Call(IRoleControllerService::onGrantDefaultRoles, + callback)); } private static final class Connection implements ServiceConnection { @@ -113,6 +109,9 @@ public class RemoteRoleControllerService { @NonNull private final Context mContext; + @NonNull + private final Handler mHandler = FgThread.getHandler(); + private boolean mBound; @Nullable @@ -161,8 +160,8 @@ public class RemoteRoleControllerService { if (DEBUG) { Slog.i(LOG_TAG, "Enqueue " + call); } - sCallbackHandler.executeOrSendMessage(PooledLambda.obtainMessage( - Connection::executeCall, this, call)); + mHandler.executeOrSendMessage(PooledLambda.obtainMessage(Connection::executeCall, this, + call)); } @WorkerThread @@ -181,7 +180,7 @@ public class RemoteRoleControllerService { @WorkerThread private void ensureBound() { - sCallbackHandler.removeCallbacks(mUnbindRunnable); + mHandler.removeCallbacks(mUnbindRunnable); if (!mBound) { Intent intent = new Intent(RoleControllerService.SERVICE_INTERFACE); intent.setPackage(mContext.getPackageManager() @@ -191,13 +190,13 @@ public class RemoteRoleControllerService { // // Note that as a result, onServiceConnected may happen not on main thread! mBound = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE, - sCallbackHandler, UserHandle.of(mUserId)); + mHandler, UserHandle.of(mUserId)); } } private void scheduleUnbind() { - sCallbackHandler.removeCallbacks(mUnbindRunnable); - sCallbackHandler.postDelayed(mUnbindRunnable, UNBIND_DELAY_MILLIS); + mHandler.removeCallbacks(mUnbindRunnable); + mHandler.postDelayed(mUnbindRunnable, UNBIND_DELAY_MILLIS); } @WorkerThread @@ -220,6 +219,9 @@ public class RemoteRoleControllerService { private final IRoleManagerCallback mCallback; @NonNull + private final Handler mHandler = FgThread.getHandler(); + + @NonNull private final Runnable mTimeoutRunnable = this::notifyTimeout; private boolean mCallbackNotified; @@ -236,7 +238,7 @@ public class RemoteRoleControllerService { Slog.i(LOG_TAG, "Executing " + this); } try { - sCallbackHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MILLIS); + mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MILLIS); mCallExecutor.execute(service, new CallbackDelegate()); } catch (RemoteException e) { Slog.e(LOG_TAG, "Error calling RoleControllerService", e); @@ -256,7 +258,7 @@ public class RemoteRoleControllerService { return; } mCallbackNotified = true; - sCallbackHandler.removeCallbacks(mTimeoutRunnable); + mHandler.removeCallbacks(mTimeoutRunnable); try { if (success) { mCallback.onSuccess(); @@ -286,14 +288,14 @@ public class RemoteRoleControllerService { @Override public void onSuccess() throws RemoteException { - sCallbackHandler.sendMessage(PooledLambda.obtainMessage( - Call::notifyCallback, Call.this, true)); + mHandler.sendMessage(PooledLambda.obtainMessage(Call::notifyCallback, Call.this, + true)); } @Override public void onFailure() throws RemoteException { - sCallbackHandler.sendMessage(PooledLambda.obtainMessage( - Call::notifyCallback, Call.this, false)); + mHandler.sendMessage(PooledLambda.obtainMessage(Call::notifyCallback, Call.this, + false)); } } } diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java index d5faab55b663..35013de6a4eb 100644 --- a/services/core/java/com/android/server/role/RoleManagerService.java +++ b/services/core/java/com/android/server/role/RoleManagerService.java @@ -22,8 +22,10 @@ import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.annotation.WorkerThread; import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.role.IOnRoleHoldersChangedListener; import android.app.role.IRoleManager; import android.app.role.IRoleManagerCallback; import android.app.role.RoleManager; @@ -33,6 +35,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManagerInternal; import android.content.pm.Signature; +import android.os.Handler; +import android.os.RemoteCallbackList; +import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; @@ -42,17 +47,25 @@ import android.util.ArraySet; import android.util.PackageUtils; import android.util.Slog; import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.BitUtils; import com.android.internal.util.CollectionUtils; +import com.android.internal.util.DumpUtils; import com.android.internal.util.FunctionalUtils; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; +import com.android.internal.util.dump.DualDumpOutputStream; +import com.android.internal.util.function.pooled.PooledLambda; +import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -67,7 +80,7 @@ import java.util.concurrent.TimeoutException; * * @see RoleManager */ -public class RoleManagerService extends SystemService { +public class RoleManagerService extends SystemService implements RoleUserState.Callback { private static final String LOG_TAG = RoleManagerService.class.getSimpleName(); @@ -94,6 +107,17 @@ public class RoleManagerService extends SystemService { private final SparseArray<RemoteRoleControllerService> mControllerServices = new SparseArray<>(); + /** + * Maps user id to its list of listeners. + */ + @GuardedBy("mLock") + @NonNull + private final SparseArray<RemoteCallbackList<IOnRoleHoldersChangedListener>> mListeners = + new SparseArray<>(); + + @NonNull + private final Handler mListenerHandler = FgThread.getHandler(); + public RoleManagerService(@NonNull Context context) { super(context); @@ -150,21 +174,16 @@ public class RoleManagerService extends SystemService { @MainThread private void performInitialGrantsIfNecessary(@UserIdInt int userId) { RoleUserState userState; - synchronized (mLock) { - userState = getUserStateLocked(userId); - } + userState = getOrCreateUserState(userId); String packagesHash = computeComponentStateHash(userId); - String lastGrantPackagesHash; - synchronized (mLock) { - lastGrantPackagesHash = userState.getLastGrantPackagesHashLocked(); - } - boolean needGrant = !Objects.equals(packagesHash, lastGrantPackagesHash); + String oldPackagesHash = userState.getPackagesHash(); + boolean needGrant = !Objects.equals(packagesHash, oldPackagesHash); if (needGrant) { // Some vital packages state has changed since last role grant // Run grants again Slog.i(LOG_TAG, "Granting default permissions..."); CompletableFuture<Void> result = new CompletableFuture<>(); - getControllerService(userId).onGrantDefaultRoles( + getOrCreateControllerService(userId).onGrantDefaultRoles( new IRoleManagerCallback.Stub() { @Override public void onSuccess() { @@ -177,9 +196,7 @@ public class RoleManagerService extends SystemService { }); try { result.get(5, TimeUnit.SECONDS); - synchronized (mLock) { - userState.setLastGrantPackagesHashLocked(packagesHash); - } + userState.setPackagesHash(packagesHash); } catch (InterruptedException | ExecutionException | TimeoutException e) { Slog.e(LOG_TAG, "Failed to grant defaults for user " + userId, e); } @@ -189,7 +206,7 @@ public class RoleManagerService extends SystemService { } @Nullable - private String computeComponentStateHash(@UserIdInt int userId) { + private static String computeComponentStateHash(@UserIdInt int userId) { PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -219,35 +236,91 @@ public class RoleManagerService extends SystemService { return PackageUtils.computeSha256Digest(out.toByteArray()); } - @GuardedBy("mLock") @NonNull - private RoleUserState getUserStateLocked(@UserIdInt int userId) { - RoleUserState userState = mUserStates.get(userId); - if (userState == null) { - userState = RoleUserState.newInstanceLocked(userId); - mUserStates.put(userId, userState); + private RoleUserState getOrCreateUserState(@UserIdInt int userId) { + synchronized (mLock) { + RoleUserState userState = mUserStates.get(userId); + if (userState == null) { + userState = new RoleUserState(userId, this); + mUserStates.put(userId, userState); + } + return userState; + } + } + + @NonNull + private RemoteRoleControllerService getOrCreateControllerService(@UserIdInt int userId) { + synchronized (mLock) { + RemoteRoleControllerService controllerService = mControllerServices.get(userId); + if (controllerService == null) { + controllerService = new RemoteRoleControllerService(userId, getContext()); + mControllerServices.put(userId, controllerService); + } + return controllerService; + } + } + + @Nullable + private RemoteCallbackList<IOnRoleHoldersChangedListener> getListeners(@UserIdInt int userId) { + synchronized (mLock) { + return mListeners.get(userId); } - return userState; } - @GuardedBy("mLock") @NonNull - private RemoteRoleControllerService getControllerService(@UserIdInt int userId) { - RemoteRoleControllerService controllerService = mControllerServices.get(userId); - if (controllerService == null) { - controllerService = new RemoteRoleControllerService(userId, getContext()); - mControllerServices.put(userId, controllerService); + private RemoteCallbackList<IOnRoleHoldersChangedListener> getOrCreateListeners( + @UserIdInt int userId) { + synchronized (mLock) { + RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = mListeners.get(userId); + if (listeners == null) { + listeners = new RemoteCallbackList<>(); + mListeners.put(userId, listeners); + } + return listeners; } - return controllerService; } private void onRemoveUser(@UserIdInt int userId) { + RemoteCallbackList<IOnRoleHoldersChangedListener> listeners; + RoleUserState userState; synchronized (mLock) { + listeners = mListeners.removeReturnOld(userId); mControllerServices.remove(userId); - RoleUserState userState = mUserStates.removeReturnOld(userId); - if (userState != null) { - userState.destroySyncLocked(); + userState = mUserStates.removeReturnOld(userId); + } + if (listeners != null) { + listeners.kill(); + } + if (userState != null) { + userState.destroy(); + } + } + + @Override + public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) { + mListenerHandler.sendMessage(PooledLambda.obtainMessage( + RoleManagerService::notifyRoleHoldersChanged, this, roleName, userId)); + } + + @WorkerThread + private void notifyRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) { + RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId); + if (listeners == null) { + return; + } + + int broadcastCount = listeners.beginBroadcast(); + try { + for (int i = 0; i < broadcastCount; i++) { + IOnRoleHoldersChangedListener listener = listeners.getBroadcastItem(i); + try { + listener.onRoleHoldersChanged(roleName, userId); + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Error calling OnRoleHoldersChangedListener", e); + } } + } finally { + listeners.finishBroadcast(); } } @@ -258,10 +331,8 @@ public class RoleManagerService extends SystemService { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); int userId = UserHandle.getUserId(getCallingUid()); - synchronized (mLock) { - RoleUserState userState = getUserStateLocked(userId); - return userState.isRoleAvailableLocked(roleName); - } + RoleUserState userState = getOrCreateUserState(userId); + return userState.isRoleAvailable(roleName); } @Override @@ -301,10 +372,8 @@ public class RoleManagerService extends SystemService { @Nullable private ArraySet<String> getRoleHoldersInternal(@NonNull String roleName, @UserIdInt int userId) { - synchronized (mLock) { - RoleUserState userState = getUserStateLocked(userId); - return userState.getRoleHoldersLocked(roleName); - } + RoleUserState userState = getOrCreateUserState(userId); + return userState.getRoleHolders(roleName); } @Override @@ -321,7 +390,7 @@ public class RoleManagerService extends SystemService { getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, "addRoleHolderAsUser"); - getControllerService(userId).onAddRoleHolder(roleName, packageName, callback); + getOrCreateControllerService(userId).onAddRoleHolder(roleName, packageName, callback); } @Override @@ -338,7 +407,7 @@ public class RoleManagerService extends SystemService { getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, "removeRoleHolderAsUser"); - getControllerService(userId).onRemoveRoleHolder(roleName, packageName, + getOrCreateControllerService(userId).onRemoveRoleHolder(roleName, packageName, callback); } @@ -355,7 +424,43 @@ public class RoleManagerService extends SystemService { getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, "clearRoleHoldersAsUser"); - getControllerService(userId).onClearRoleHolders(roleName, callback); + getOrCreateControllerService(userId).onClearRoleHolders(roleName, callback); + } + + @Override + public void addOnRoleHoldersChangedListenerAsUser( + @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) { + Preconditions.checkNotNull(listener, "listener cannot be null"); + if (!mUserManagerInternal.exists(userId)) { + Slog.e(LOG_TAG, "user " + userId + " does not exist"); + return; + } + userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser"); + getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS, + "addOnRoleHoldersChangedListenerAsUser"); + + RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getOrCreateListeners( + userId); + listeners.register(listener); + } + + @Override + public void removeOnRoleHoldersChangedListenerAsUser( + @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) { + Preconditions.checkNotNull(listener, "listener cannot be null"); + if (!mUserManagerInternal.exists(userId)) { + Slog.e(LOG_TAG, "user " + userId + " does not exist"); + return; + } + userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser"); + getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS, + "removeOnRoleHoldersChangedListenerAsUser"); + + RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId); + if (listener == null) { + return; + } + listeners.unregister(listener); } @Override @@ -366,10 +471,8 @@ public class RoleManagerService extends SystemService { "setRoleNamesFromController"); int userId = UserHandle.getCallingUserId(); - synchronized (mLock) { - RoleUserState userState = getUserStateLocked(userId); - userState.setRoleNamesLocked(roleNames); - } + RoleUserState userState = getOrCreateUserState(userId); + userState.setRoleNames(roleNames); } @Override @@ -382,10 +485,8 @@ public class RoleManagerService extends SystemService { "addRoleHolderFromController"); int userId = UserHandle.getCallingUserId(); - synchronized (mLock) { - RoleUserState userState = getUserStateLocked(userId); - return userState.addRoleHolderLocked(roleName, packageName); - } + RoleUserState userState = getOrCreateUserState(userId); + return userState.addRoleHolder(roleName, packageName); } @Override @@ -398,10 +499,8 @@ public class RoleManagerService extends SystemService { "removeRoleHolderFromController"); int userId = UserHandle.getCallingUserId(); - synchronized (mLock) { - RoleUserState userState = getUserStateLocked(userId); - return userState.removeRoleHolderLocked(roleName, packageName); - } + RoleUserState userState = getOrCreateUserState(userId); + return userState.removeRoleHolder(roleName, packageName); } @CheckResult @@ -417,5 +516,34 @@ public class RoleManagerService extends SystemService { new RoleManagerShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver); } + + @Override + protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, + @Nullable String[] args) { + if (!DumpUtils.checkDumpPermission(getContext(), LOG_TAG, fout)) { + return; + } + + boolean dumpAsProto = args != null && ArrayUtils.contains(args, "--proto"); + DualDumpOutputStream dumpOutputStream; + if (dumpAsProto) { + dumpOutputStream = new DualDumpOutputStream(new ProtoOutputStream(fd)); + } else { + fout.println("ROLE MANAGER STATE (dumpsys role):"); + dumpOutputStream = new DualDumpOutputStream(new IndentingPrintWriter(fout, " ")); + } + + int[] userIds = mUserManagerInternal.getUserIds(); + int userIdsLength = userIds.length; + for (int i = 0; i < userIdsLength; i++) { + int userId = userIds[i]; + + RoleUserState userState = getOrCreateUserState(userId); + userState.dump(dumpOutputStream, "user_states", + RoleManagerServiceDumpProto.USER_STATES); + } + + dumpOutputStream.flush(); + } } } diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java index ce0597435f71..d55e261986d0 100644 --- a/services/core/java/com/android/server/role/RoleUserState.java +++ b/services/core/java/com/android/server/role/RoleUserState.java @@ -32,6 +32,7 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; import com.android.internal.util.CollectionUtils; +import com.android.internal.util.dump.DualDumpOutputStream; import com.android.internal.util.function.pooled.PooledLambda; import libcore.io.IoUtils; @@ -61,7 +62,6 @@ public class RoleUserState { private static final String ROLES_FILE_NAME = "roles.xml"; private static final long WRITE_DELAY_MILLIS = 200; - private static final long MAX_WRITE_DELAY_MILLIS = 2000; private static final String TAG_ROLES = "roles"; private static final String TAG_ROLE = "role"; @@ -73,54 +73,57 @@ public class RoleUserState { @UserIdInt private final int mUserId; - @GuardedBy("RoleManagerService.mLock") + @NonNull + private final Callback mCallback; + + @NonNull + private final Object mLock = new Object(); + + @GuardedBy("mLock") private int mVersion = VERSION_UNDEFINED; - @GuardedBy("RoleManagerService.mLock") + @GuardedBy("mLock") @Nullable - private String mLastGrantPackagesHash; + private String mPackagesHash; /** * Maps role names to its holders' package names. The values should never be null. */ - @GuardedBy("RoleManagerService.mLock") + @GuardedBy("mLock") @NonNull private ArrayMap<String, ArraySet<String>> mRoles = new ArrayMap<>(); - @GuardedBy("RoleManagerService.mLock") - private long mWritePendingSinceMillis; + @GuardedBy("mLock") + private boolean mWriteScheduled; - @GuardedBy("RoleManagerService.mLock") + @GuardedBy("mLock") private boolean mDestroyed; @NonNull private final Handler mWriteHandler = new Handler(BackgroundThread.getHandler().getLooper()); - private RoleUserState(@UserIdInt int userId) { - mUserId = userId; - - readSyncLocked(); - } - /** - * Create a new instance of user state, and read its state from disk if previously persisted. - * - * @param userId the user id for the new user state + * Create a new user state, and read its state from disk if previously persisted. * - * @return the new user state + * @param userId the user id for this user state + * @param callback the callback for this user state */ - @GuardedBy("RoleManagerService.mLock") - public static RoleUserState newInstanceLocked(@UserIdInt int userId) { - return new RoleUserState(userId); + public RoleUserState(@UserIdInt int userId, @NonNull Callback callback) { + mUserId = userId; + mCallback = callback; + + readFile(); } /** * Get the version of this user state. */ - @GuardedBy("RoleManagerService.mLock") - public int getVersionLocked() { - throwIfDestroyedLocked(); - return mVersion; + public int getVersion() { + synchronized (mLock) { + throwIfDestroyedLocked(); + + return mVersion; + } } /** @@ -128,14 +131,16 @@ public class RoleUserState { * * @param version the version to set */ - @GuardedBy("RoleManagerService.mLock") - public void setVersionLocked(int version) { - throwIfDestroyedLocked(); - if (mVersion == version) { - return; + public void setVersion(int version) { + synchronized (mLock) { + throwIfDestroyedLocked(); + + if (mVersion == version) { + return; + } + mVersion = version; + scheduleWriteFileLocked(); } - mVersion = version; - writeAsyncLocked(); } /** @@ -143,24 +148,28 @@ public class RoleUserState { * * @return the hash representing the state of packages */ - @GuardedBy("RoleManagerService.mLock") - public String getLastGrantPackagesHashLocked() { - return mLastGrantPackagesHash; + @Nullable + public String getPackagesHash() { + synchronized (mLock) { + return mPackagesHash; + } } /** * Set the hash representing the state of packages during the last time initial grants was run. * - * @param lastGrantPackagesHash the hash representing the state of packages + * @param packagesHash the hash representing the state of packages */ - @GuardedBy("RoleManagerService.mLock") - public void setLastGrantPackagesHashLocked(@Nullable String lastGrantPackagesHash) { - throwIfDestroyedLocked(); - if (Objects.equals(mLastGrantPackagesHash, lastGrantPackagesHash)) { - return; + public void setPackagesHash(@Nullable String packagesHash) { + synchronized (mLock) { + throwIfDestroyedLocked(); + + if (Objects.equals(mPackagesHash, packagesHash)) { + return; + } + mPackagesHash = packagesHash; + scheduleWriteFileLocked(); } - mLastGrantPackagesHash = lastGrantPackagesHash; - writeAsyncLocked(); } /** @@ -170,10 +179,12 @@ public class RoleUserState { * * @return whether the role is available */ - @GuardedBy("RoleManagerService.mLock") - public boolean isRoleAvailableLocked(@NonNull String roleName) { - throwIfDestroyedLocked(); - return mRoles.containsKey(roleName); + public boolean isRoleAvailable(@NonNull String roleName) { + synchronized (mLock) { + throwIfDestroyedLocked(); + + return mRoles.containsKey(roleName); + } } /** @@ -183,11 +194,13 @@ public class RoleUserState { * * @return the set of role holders. {@code null} should not be returned and indicates an issue. */ - @GuardedBy("RoleManagerService.mLock") @Nullable - public ArraySet<String> getRoleHoldersLocked(@NonNull String roleName) { - throwIfDestroyedLocked(); - return mRoles.get(roleName); + public ArraySet<String> getRoleHolders(@NonNull String roleName) { + synchronized (mLock) { + throwIfDestroyedLocked(); + + return new ArraySet<>(mRoles.get(roleName)); + } } /** @@ -195,33 +208,40 @@ public class RoleUserState { * * @param roleNames the names of all the available roles */ - @GuardedBy("RoleManagerService.mLock") - public void setRoleNamesLocked(@NonNull List<String> roleNames) { - throwIfDestroyedLocked(); - boolean changed = false; - for (int i = mRoles.size() - 1; i >= 0; i--) { - String roleName = mRoles.keyAt(i); - if (!roleNames.contains(roleName)) { - ArraySet<String> packageNames = mRoles.valueAt(i); - if (!packageNames.isEmpty()) { - Slog.e(LOG_TAG, "Holders of a removed role should have been cleaned up, role: " - + roleName + ", holders: " + packageNames); + public void setRoleNames(@NonNull List<String> roleNames) { + synchronized (mLock) { + throwIfDestroyedLocked(); + + boolean changed = false; + + for (int i = mRoles.size() - 1; i >= 0; i--) { + String roleName = mRoles.keyAt(i); + + if (!roleNames.contains(roleName)) { + ArraySet<String> packageNames = mRoles.valueAt(i); + if (!packageNames.isEmpty()) { + Slog.e(LOG_TAG, "Holders of a removed role should have been cleaned up," + + " role: " + roleName + ", holders: " + packageNames); + } + mRoles.removeAt(i); + changed = true; } - mRoles.removeAt(i); - changed = true; } - } - int roleNamesSize = roleNames.size(); - for (int i = 0; i < roleNamesSize; i++) { - String roleName = roleNames.get(i); - if (!mRoles.containsKey(roleName)) { - mRoles.put(roleName, new ArraySet<>()); - Slog.i(LOG_TAG, "Added new role: " + roleName); - changed = true; + + int roleNamesSize = roleNames.size(); + for (int i = 0; i < roleNamesSize; i++) { + String roleName = roleNames.get(i); + + if (!mRoles.containsKey(roleName)) { + mRoles.put(roleName, new ArraySet<>()); + Slog.i(LOG_TAG, "Added new role: " + roleName); + changed = true; + } + } + + if (changed) { + scheduleWriteFileLocked(); } - } - if (changed) { - writeAsyncLocked(); } } @@ -235,18 +255,26 @@ public class RoleUserState { * indicates an issue. */ @CheckResult - @GuardedBy("RoleManagerService.mLock") - public boolean addRoleHolderLocked(@NonNull String roleName, @NonNull String packageName) { - throwIfDestroyedLocked(); - ArraySet<String> roleHolders = mRoles.get(roleName); - if (roleHolders == null) { - Slog.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName - + ", package: " + packageName); - return false; + public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) { + boolean changed; + + synchronized (mLock) { + throwIfDestroyedLocked(); + + ArraySet<String> roleHolders = mRoles.get(roleName); + if (roleHolders == null) { + Slog.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName + + ", package: " + packageName); + return false; + } + changed = roleHolders.add(packageName); + if (changed) { + scheduleWriteFileLocked(); + } } - boolean changed = roleHolders.add(packageName); + if (changed) { - writeAsyncLocked(); + mCallback.onRoleHoldersChanged(roleName, mUserId); } return true; } @@ -261,18 +289,27 @@ public class RoleUserState { * indicates an issue. */ @CheckResult - @GuardedBy("RoleManagerService.mLock") - public boolean removeRoleHolderLocked(@NonNull String roleName, @NonNull String packageName) { - throwIfDestroyedLocked(); - ArraySet<String> roleHolders = mRoles.get(roleName); - if (roleHolders == null) { - Slog.e(LOG_TAG, "Cannot remove role holder for unknown role, role: " + roleName - + ", package: " + packageName); - return false; + public boolean removeRoleHolder(@NonNull String roleName, @NonNull String packageName) { + boolean changed; + + synchronized (mLock) { + throwIfDestroyedLocked(); + + ArraySet<String> roleHolders = mRoles.get(roleName); + if (roleHolders == null) { + Slog.e(LOG_TAG, "Cannot remove role holder for unknown role, role: " + roleName + + ", package: " + packageName); + return false; + } + + changed = roleHolders.remove(packageName); + if (changed) { + scheduleWriteFileLocked(); + } } - boolean changed = roleHolders.remove(packageName); + if (changed) { - writeAsyncLocked(); + mCallback.onRoleHoldersChanged(roleName, mUserId); } return true; } @@ -280,44 +317,34 @@ public class RoleUserState { /** * Schedule writing the state to file. */ - @GuardedBy("RoleManagerService.mLock") - private void writeAsyncLocked() { + @GuardedBy("mLock") + private void scheduleWriteFileLocked() { throwIfDestroyedLocked(); - ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>(); - for (int i = 0, size = CollectionUtils.size(mRoles); i < size; ++i) { - String roleName = mRoles.keyAt(i); - ArraySet<String> roleHolders = mRoles.valueAt(i); - - roleHolders = new ArraySet<>(roleHolders); - roles.put(roleName, roleHolders); + if (!mWriteScheduled) { + mWriteHandler.sendMessageDelayed(PooledLambda.obtainMessage(RoleUserState::writeFile, + this), WRITE_DELAY_MILLIS); + mWriteScheduled = true; } + } - long currentTimeMillis = System.currentTimeMillis(); - long writeDelayMillis; - if (!mWriteHandler.hasMessagesOrCallbacks()) { - mWritePendingSinceMillis = currentTimeMillis; - writeDelayMillis = WRITE_DELAY_MILLIS; - } else { - mWriteHandler.removeCallbacksAndMessages(null); - long writePendingDurationMillis = currentTimeMillis - mWritePendingSinceMillis; - if (writePendingDurationMillis >= MAX_WRITE_DELAY_MILLIS) { - writeDelayMillis = 0; - } else { - long maxWriteDelayMillis = Math.max(MAX_WRITE_DELAY_MILLIS - - writePendingDurationMillis, 0); - writeDelayMillis = Math.min(WRITE_DELAY_MILLIS, maxWriteDelayMillis); + @WorkerThread + private void writeFile() { + int version; + String packagesHash; + ArrayMap<String, ArraySet<String>> roles; + synchronized (mLock) { + if (mDestroyed) { + return; } - } - mWriteHandler.sendMessageDelayed(PooledLambda.obtainMessage(RoleUserState::writeSync, this, - mVersion, mLastGrantPackagesHash, roles), writeDelayMillis); - Slog.i(LOG_TAG, "Scheduled writing roles.xml"); - } + mWriteScheduled = false; + + version = mVersion; + packagesHash = mPackagesHash; + roles = snapshotRolesLocked(); + } - @WorkerThread - private void writeSync(int version, @Nullable String packagesHash, - @NonNull ArrayMap<String, ArraySet<String>> roles) { AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId); FileOutputStream out = null; try { @@ -384,18 +411,19 @@ public class RoleUserState { /** * Read the state from file. */ - @GuardedBy("RoleManagerService.mLock") - private void readSyncLocked() { - File file = getFile(mUserId); - try (FileInputStream in = new AtomicFile(file).openRead()) { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(in, null); - parseXmlLocked(parser); - Slog.i(LOG_TAG, "Read roles.xml successfully"); - } catch (FileNotFoundException e) { - Slog.i(LOG_TAG, "roles.xml not found"); - } catch (XmlPullParserException | IOException e) { - throw new IllegalStateException("Failed to parse roles.xml: " + file, e); + private void readFile() { + synchronized (mLock) { + File file = getFile(mUserId); + try (FileInputStream in = new AtomicFile(file).openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parseXmlLocked(parser); + Slog.i(LOG_TAG, "Read roles.xml successfully"); + } catch (FileNotFoundException e) { + Slog.i(LOG_TAG, "roles.xml not found"); + } catch (XmlPullParserException | IOException e) { + throw new IllegalStateException("Failed to parse roles.xml: " + file, e); + } } } @@ -421,7 +449,7 @@ public class RoleUserState { private void parseRolesLocked(@NonNull XmlPullParser parser) throws IOException, XmlPullParserException { mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION)); - mLastGrantPackagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH); + mPackagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH); mRoles.clear(); int type; @@ -465,18 +493,76 @@ public class RoleUserState { } /** - * Destroy this state and delete the corresponding file. Any pending writes to the file will be - * cancelled and any future interaction with this state will throw an exception. + * Dump this user state. + * + * @param dumpOutputStream the output stream to dump to */ - @GuardedBy("RoleManagerService.mLock") - public void destroySyncLocked() { - throwIfDestroyedLocked(); - mWriteHandler.removeCallbacksAndMessages(null); - getFile(mUserId).delete(); - mDestroyed = true; + public void dump(@NonNull DualDumpOutputStream dumpOutputStream, @NonNull String fieldName, + long fieldId) { + int version; + String packagesHash; + ArrayMap<String, ArraySet<String>> roles; + synchronized (mLock) { + throwIfDestroyedLocked(); + + version = mVersion; + packagesHash = mPackagesHash; + roles = snapshotRolesLocked(); + } + + long fieldToken = dumpOutputStream.start(fieldName, fieldId); + dumpOutputStream.write("user_id", RoleUserStateProto.USER_ID, mUserId); + dumpOutputStream.write("version", RoleUserStateProto.VERSION, version); + dumpOutputStream.write("packages_hash", RoleUserStateProto.PACKAGES_HASH, packagesHash); + + int rolesSize = roles.size(); + for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) { + String roleName = roles.keyAt(rolesIndex); + ArraySet<String> roleHolders = roles.valueAt(rolesIndex); + + long rolesToken = dumpOutputStream.start("roles", RoleUserStateProto.ROLES); + dumpOutputStream.write("name", RoleProto.NAME, roleName); + + int roleHoldersSize = roleHolders.size(); + for (int roleHoldersIndex = 0; roleHoldersIndex < roleHoldersSize; roleHoldersIndex++) { + String roleHolder = roleHolders.valueAt(roleHoldersIndex); + + dumpOutputStream.write("holders", RoleProto.HOLDERS, roleHolder); + } + + dumpOutputStream.end(rolesToken); + } + + dumpOutputStream.end(fieldToken); } - @GuardedBy("RoleManagerService.mLock") + @GuardedBy("mLock") + private ArrayMap<String, ArraySet<String>> snapshotRolesLocked() { + ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>(); + for (int i = 0, size = CollectionUtils.size(mRoles); i < size; ++i) { + String roleName = mRoles.keyAt(i); + ArraySet<String> roleHolders = mRoles.valueAt(i); + + roleHolders = new ArraySet<>(roleHolders); + roles.put(roleName, roleHolders); + } + return roles; + } + + /** + * Destroy this user state and delete the corresponding file. Any pending writes to the file + * will be cancelled, and any future interaction with this state will throw an exception. + */ + public void destroy() { + synchronized (mLock) { + throwIfDestroyedLocked(); + mWriteHandler.removeCallbacksAndMessages(null); + getFile(mUserId).delete(); + mDestroyed = true; + } + } + + @GuardedBy("mLock") private void throwIfDestroyedLocked() { if (mDestroyed) { throw new IllegalStateException("This RoleUserState has already been destroyed"); @@ -486,4 +572,18 @@ public class RoleUserState { private static @NonNull File getFile(@UserIdInt int userId) { return new File(Environment.getUserSystemDirectory(userId), ROLES_FILE_NAME); } + + /** + * Callback for a user state. + */ + public interface Callback { + + /** + * Called when the holders of roles are changed. + * + * @param roleName the name of the role whose holders are changed + * @param userId the user id for this role holder change + */ + void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId); + } } diff --git a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-cs/strings.xml b/services/core/java/com/android/server/signedconfig/InvalidConfigException.java index d923a3cc4376..f01baa4b0c5d 100644 --- a/packages/overlays/ExperimentNavigationBarSlim24Overlay/res/values-cs/strings.xml +++ b/services/core/java/com/android/server/signedconfig/InvalidConfigException.java @@ -1,13 +1,11 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright (c) 2018, The Android Open Source Project +/* + * Copyright (C) 2018 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, @@ -15,9 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - --> -<resources xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Úzký navigační panel (experiment)"</string> -</resources> +package com.android.server.signedconfig; + +/** + * Thrown when there is a problem parsing the config embedded in an APK. + */ +public class InvalidConfigException extends Exception { + + public InvalidConfigException(String message) { + super(message); + } + + public InvalidConfigException(String message, Exception cause) { + super(message, cause); + } + + +} diff --git a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java new file mode 100644 index 000000000000..944db84acc71 --- /dev/null +++ b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 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.signedconfig; + +import android.os.Build; +import android.util.Slog; + +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +/** + * Helper class for verifying config signatures. + */ +public class SignatureVerifier { + + private static final String TAG = "SignedConfig"; + private static final boolean DBG = false; + + private static final String DEBUG_KEY = + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60" + + "pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ=="; + + private final PublicKey mDebugKey; + + public SignatureVerifier() { + mDebugKey = createKey(DEBUG_KEY); + } + + private static PublicKey createKey(String base64) { + EncodedKeySpec keySpec; + try { + byte[] key = Base64.getDecoder().decode(base64); + keySpec = new X509EncodedKeySpec(key); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Failed to base64 decode public key", e); + return null; + } + try { + KeyFactory factory = KeyFactory.getInstance("EC"); + return factory.generatePublic(keySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + Slog.e(TAG, "Failed to construct public key", e); + return null; + } + } + + /** + * Verify a signature for signed config. + * + * @param config Config as read from APK meta-data. + * @param base64Signature Signature as read from APK meta-data. + * @return {@code true} iff the signature was successfully verified. + */ + public boolean verifySignature(String config, String base64Signature) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + byte[] signature; + try { + signature = Base64.getDecoder().decode(base64Signature); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Failed to base64 decode signature"); + return false; + } + byte[] data = config.getBytes(StandardCharsets.UTF_8); + if (DBG) Slog.i(TAG, "Data: " + Base64.getEncoder().encodeToString(data)); + + if (Build.IS_DEBUGGABLE) { + if (mDebugKey != null) { + if (DBG) Slog.w(TAG, "Trying to verify signature using debug key"); + Signature verifier = Signature.getInstance("SHA256withECDSA"); + verifier.initVerify(mDebugKey); + verifier.update(data); + if (verifier.verify(signature)) { + Slog.i(TAG, "Verified config using debug key"); + return true; + } else { + if (DBG) Slog.i(TAG, "Config verification failed using debug key"); + } + } else { + Slog.w(TAG, "Debuggable build, but have no debug key"); + } + } + // TODO verify production key. + Slog.w(TAG, "NO PRODUCTION KEY YET, FAILING VERIFICATION"); + return false; + } +} diff --git a/services/core/java/com/android/server/signedconfig/SignedConfig.java b/services/core/java/com/android/server/signedconfig/SignedConfig.java new file mode 100644 index 000000000000..560a1e1cfe6c --- /dev/null +++ b/services/core/java/com/android/server/signedconfig/SignedConfig.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2018 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.signedconfig; + +import com.android.internal.annotations.VisibleForTesting; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Represents signed configuration. + * + * <p>This configuration should only be used if the signature has already been verified. + * + * This class also parses signed config from JSON. The format expected is: + * <pre> + * { + * "version": 1 + * "config": [ + * { + * "min_sdk": 28, + * "max_sdk": 29, + * "values": { + * "key": "value", + * "key2": "value2" + * ... + * } + * }, + * ... + * ], + * } + * </pre> + */ +public class SignedConfig { + + private static final String KEY_VERSION = "version"; + private static final String KEY_CONFIG = "config"; + + private static final String CONFIG_KEY_MIN_SDK = "min_sdk"; + private static final String CONFIG_KEY_MAX_SDK = "max_sdk"; + private static final String CONFIG_KEY_VALUES = "values"; + + /** + * Represents config values targeting an SDK range. + */ + public static class PerSdkConfig { + public final int minSdk; + public final int maxSdk; + public final Map<String, String> values; + + public PerSdkConfig(int minSdk, int maxSdk, Map<String, String> values) { + this.minSdk = minSdk; + this.maxSdk = maxSdk; + this.values = Collections.unmodifiableMap(values); + } + + } + + public final int version; + public final List<PerSdkConfig> perSdkConfig; + + public SignedConfig(int version, List<PerSdkConfig> perSdkConfig) { + this.version = version; + this.perSdkConfig = Collections.unmodifiableList(perSdkConfig); + } + + /** + * Find matching sdk config for a given SDK level. + * + * @param sdkVersion SDK version of device. + * @return Matching config, of {@code null} if there is none. + */ + public PerSdkConfig getMatchingConfig(int sdkVersion) { + for (PerSdkConfig config : perSdkConfig) { + if (config.minSdk <= sdkVersion && sdkVersion <= config.maxSdk) { + return config; + } + } + // nothing matching + return null; + } + + /** + * Parse configuration from an APK. + * + * @param config Config string as read from the APK metadata. + * @param allowedKeys Set of allowed keys in the config. Any key/value mapping for a key not in + * this set will result in an {@link InvalidConfigException} being thrown. + * @param keyValueMappers Mappings for values per key. The keys in the top level map should be + * a subset of {@code allowedKeys}. The keys in the inner map indicate + * the set of allowed values for that keys value. This map will be + * applied to the value in the configuration. This is intended to allow + * enum-like values to be encoded as strings in the configuration, and + * mapped back to integers when the configuration is parsed. + * + * <p>Any config key with a value that does not appear in the + * corresponding map will result in an {@link InvalidConfigException} + * being thrown. + * @return Parsed configuration. + * @throws InvalidConfigException If there's a problem parsing the config. + */ + public static SignedConfig parse(String config, Set<String> allowedKeys, + Map<String, Map<String, String>> keyValueMappers) + throws InvalidConfigException { + try { + JSONObject json = new JSONObject(config); + int version = json.getInt(KEY_VERSION); + + JSONArray perSdkConfig = json.getJSONArray(KEY_CONFIG); + List<PerSdkConfig> parsedConfigs = new ArrayList<>(); + for (int i = 0; i < perSdkConfig.length(); ++i) { + parsedConfigs.add(parsePerSdkConfig(perSdkConfig.getJSONObject(i), allowedKeys, + keyValueMappers)); + } + + return new SignedConfig(version, parsedConfigs); + } catch (JSONException e) { + throw new InvalidConfigException("Could not parse JSON", e); + } + + } + + private static CharSequence quoted(Object s) { + if (s == null) { + return "null"; + } else { + return "\"" + s + "\""; + } + } + + @VisibleForTesting + static PerSdkConfig parsePerSdkConfig(JSONObject json, Set<String> allowedKeys, + Map<String, Map<String, String>> keyValueMappers) + throws JSONException, InvalidConfigException { + int minSdk = json.getInt(CONFIG_KEY_MIN_SDK); + int maxSdk = json.getInt(CONFIG_KEY_MAX_SDK); + JSONObject valuesJson = json.getJSONObject(CONFIG_KEY_VALUES); + Map<String, String> values = new HashMap<>(); + for (String key : valuesJson.keySet()) { + Object valueObject = valuesJson.get(key); + String value = valueObject == JSONObject.NULL || valueObject == null + ? null + : valueObject.toString(); + if (!allowedKeys.contains(key)) { + throw new InvalidConfigException("Config key " + key + " is not allowed"); + } + if (keyValueMappers.containsKey(key)) { + Map<String, String> mapper = keyValueMappers.get(key); + if (!mapper.containsKey(value)) { + throw new InvalidConfigException( + "Config key " + key + " contains unsupported value " + quoted(value)); + } + value = mapper.get(value); + } + values.put(key, value); + } + return new PerSdkConfig(minSdk, maxSdk, values); + } + +} diff --git a/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java b/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java index 7ce071faab04..4908964109ad 100644 --- a/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java +++ b/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java @@ -17,12 +17,119 @@ package com.android.server.signedconfig; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.Build; +import android.provider.Settings; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; + +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Set; class SignedConfigApplicator { - static void applyConfig(Context context, String config, String signature) { - //TODO verify signature - //TODO parse & apply config + private static final String TAG = "SignedConfig"; + + private static final Set<String> ALLOWED_KEYS = Collections.unmodifiableSet(new ArraySet<>( + Arrays.asList( + Settings.Global.HIDDEN_API_POLICY, + Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS + ))); + + private static final Map<String, String> HIDDEN_API_POLICY_KEY_MAP = makeMap( + "DEFAULT", String.valueOf(ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT), + "DISABLED", String.valueOf(ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED), + "JUST_WARN", String.valueOf(ApplicationInfo.HIDDEN_API_ENFORCEMENT_JUST_WARN), + "ENABLED", String.valueOf(ApplicationInfo.HIDDEN_API_ENFORCEMENT_ENABLED) + ); + + private static final Map<String, Map<String, String>> KEY_VALUE_MAPPERS = makeMap( + Settings.Global.HIDDEN_API_POLICY, HIDDEN_API_POLICY_KEY_MAP + ); + + private static <K, V> Map<K, V> makeMap(Object... keyValuePairs) { + if (keyValuePairs.length % 2 != 0) { + throw new IllegalArgumentException(); + } + final int len = keyValuePairs.length / 2; + ArrayMap<K, V> m = new ArrayMap<>(len); + for (int i = 0; i < len; ++i) { + m.put((K) keyValuePairs[i * 2], (V) keyValuePairs[(i * 2) + 1]); + } + return Collections.unmodifiableMap(m); + + } + + private final Context mContext; + private final String mSourcePackage; + private final SignatureVerifier mVerifier; + + SignedConfigApplicator(Context context, String sourcePackage) { + mContext = context; + mSourcePackage = sourcePackage; + mVerifier = new SignatureVerifier(); } + private boolean checkSignature(String data, String signature) { + try { + return mVerifier.verifySignature(data, signature); + } catch (GeneralSecurityException e) { + Slog.e(TAG, "Failed to verify signature", e); + return false; + } + } + + private int getCurrentConfigVersion() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.SIGNED_CONFIG_VERSION, 0); + } + + private void updateCurrentConfig(int version, Map<String, String> values) { + for (Map.Entry<String, String> e: values.entrySet()) { + Settings.Global.putString( + mContext.getContentResolver(), + e.getKey(), + e.getValue()); + } + Settings.Global.putInt( + mContext.getContentResolver(), Settings.Global.SIGNED_CONFIG_VERSION, version); + } + + + void applyConfig(String configStr, String signature) { + if (!checkSignature(configStr, signature)) { + Slog.e(TAG, "Signature check on signed configuration in package " + mSourcePackage + + " failed; ignoring"); + return; + } + SignedConfig config; + try { + config = SignedConfig.parse(configStr, ALLOWED_KEYS, KEY_VALUE_MAPPERS); + } catch (InvalidConfigException e) { + Slog.e(TAG, "Failed to parse config from package " + mSourcePackage, e); + return; + } + int currentVersion = getCurrentConfigVersion(); + if (currentVersion >= config.version) { + Slog.i(TAG, "Config from package " + mSourcePackage + " is older than existing: " + + config.version + "<=" + currentVersion); + return; + } + // We have new config! + Slog.i(TAG, "Got new signed config from package " + mSourcePackage + ": version " + + config.version + " replacing existing version " + currentVersion); + SignedConfig.PerSdkConfig matchedConfig = + config.getMatchingConfig(Build.VERSION.SDK_INT); + if (matchedConfig == null) { + Slog.i(TAG, "Config is not applicable to current SDK version; ignoring"); + return; + } + + Slog.i(TAG, "Updating signed config to version " + config.version); + updateCurrentConfig(config.version, matchedConfig.values); + } } diff --git a/services/core/java/com/android/server/signedconfig/SignedConfigService.java b/services/core/java/com/android/server/signedconfig/SignedConfigService.java index 148568628397..84ce93f04c74 100644 --- a/services/core/java/com/android/server/signedconfig/SignedConfigService.java +++ b/services/core/java/com/android/server/signedconfig/SignedConfigService.java @@ -29,6 +29,9 @@ import android.util.Slog; import com.android.server.LocalServices; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + /** * Signed config service. This is not an Android Service, but just owns a broadcast receiver for * receiving package install and update notifications from the package manager. @@ -81,11 +84,19 @@ public class SignedConfigService { && metaData.containsKey(KEY_CONFIG_SIGNATURE)) { String config = metaData.getString(KEY_CONFIG); String signature = metaData.getString(KEY_CONFIG_SIGNATURE); + try { + // Base64 encoding is standard (not URL safe) encoding: RFC4648 + config = new String(Base64.getDecoder().decode(config), StandardCharsets.UTF_8); + } catch (IllegalArgumentException iae) { + Slog.e(TAG, "Failed to base64 decode config from " + packageName); + return; + } if (DBG) { Slog.d(TAG, "Got signed config: " + config); Slog.d(TAG, "Got config signature: " + signature); } - SignedConfigApplicator.applyConfig(mContext, config, signature); + new SignedConfigApplicator(mContext, packageName).applyConfig( + config, signature); } else { if (DBG) Slog.d(TAG, "Package has no config/signature."); } diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index c8a68b44c796..6b0419e0f7ad 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -23,7 +23,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.content.pm.PackageManager; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -136,6 +135,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi throws RemoteException { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); + validateInput(mContext, request.getCallingPackageName()); synchronized (mLock) { UserState userState = getCallingUserStateLocked(); @@ -158,6 +158,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi throws RemoteException { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); + validateInput(mContext, request.getCallingPackageName()); synchronized (mLock) { UserState userState = getCallingUserStateLocked(); @@ -180,6 +181,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi throws RemoteException { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); + validateInput(mContext, request.getCallingPackageName()); synchronized (mLock) { UserState userState = getCallingUserStateLocked(); @@ -199,7 +201,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi public void onSelectionEvent( TextClassificationSessionId sessionId, SelectionEvent event) throws RemoteException { Preconditions.checkNotNull(event); - validateInput(event.getPackageName(), mContext); + validateInput(mContext, event.getPackageName()); synchronized (mLock) { UserState userState = getCallingUserStateLocked(); @@ -220,6 +222,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi ITextLanguageCallback callback) throws RemoteException { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); + validateInput(mContext, request.getCallingPackageName()); synchronized (mLock) { UserState userState = getCallingUserStateLocked(); @@ -242,6 +245,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi IConversationActionsCallback callback) throws RemoteException { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); + validateInput(mContext, request.getCallingPackageName()); synchronized (mLock) { UserState userState = getCallingUserStateLocked(); @@ -263,7 +267,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi throws RemoteException { Preconditions.checkNotNull(sessionId); Preconditions.checkNotNull(classificationContext); - validateInput(classificationContext.getPackageName(), mContext); + validateInput(mContext, classificationContext.getPackageName()); synchronized (mLock) { UserState userState = getCallingUserStateLocked(); @@ -398,15 +402,17 @@ public final class TextClassificationManagerService extends ITextClassifierServi e -> Slog.d(LOG_TAG, "Error " + opDesc + ": " + e.getMessage())); } - private static void validateInput(String packageName, Context context) + private static void validateInput(Context context, @Nullable String packageName) throws RemoteException { + if (packageName == null) return; + try { final int uid = context.getPackageManager() .getPackageUidAsUser(packageName, UserHandle.getCallingUserId()); Preconditions.checkArgument(Binder.getCallingUid() == uid); - } catch (IllegalArgumentException | NullPointerException | - PackageManager.NameNotFoundException e) { - throw new RemoteException(e.getMessage()); + } catch (Exception e) { + throw new RemoteException( + String.format("Invalid package: name=%s, error=%s", packageName, e)); } } diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java index 7bf2c9401294..10542d52e880 100644 --- a/services/core/java/com/android/server/wm/ActivityDisplay.java +++ b/services/core/java/com/android/server/wm/ActivityDisplay.java @@ -48,12 +48,16 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.RootActivityContainer.FindTaskResult; import static com.android.server.wm.RootActivityContainer.TAG_STATES; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.WindowConfiguration; import android.content.res.Configuration; import android.graphics.Point; +import android.os.IBinder; import android.os.UserHandle; import android.util.IntArray; import android.util.Slog; @@ -86,6 +90,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> private ActivityTaskManagerService mService; private RootActivityContainer mRootActivityContainer; + // TODO: Remove once unification is complete. + DisplayContent mDisplayContent; /** Actual Display this object tracks. */ int mDisplayId; Display mDisplay; @@ -138,8 +144,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> // Used in updating the display size private Point mTmpDisplaySize = new Point(); - private DisplayWindowController mWindowContainerController; - private final FindTaskResult mTmpFindTaskResult = new FindTaskResult(); ActivityDisplay(RootActivityContainer root, Display display) { @@ -147,19 +151,15 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> mService = root.mService; mDisplayId = display.getDisplayId(); mDisplay = display; - mWindowContainerController = createWindowContainerController(); + mDisplayContent = createDisplayContent(); updateBounds(); } - protected DisplayWindowController createWindowContainerController() { - return new DisplayWindowController(mDisplay, this); - } - - DisplayWindowController getWindowContainerController() { - return mWindowContainerController; + protected DisplayContent createDisplayContent() { + return mService.mWindowManager.mRoot.createDisplayContent(mDisplay, this); } - void updateBounds() { + private void updateBounds() { mDisplay.getRealSize(mTmpDisplaySize); setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); } @@ -178,12 +178,15 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } updateBounds(); - mWindowContainerController.onDisplayChanged(); + if (mDisplayContent != null) { + mDisplayContent.updateDisplayInfo(); + mService.mWindowManager.requestTraversal(); + } } @Override public void onInitializeOverrideConfiguration(Configuration config) { - getOverrideConfiguration().updateFrom(config); + getRequestedOverrideConfiguration().updateFrom(config); } void addChild(ActivityStack stack, int position) { @@ -270,9 +273,9 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> // ActivityStack#getWindowContainerController() can be null. In this special case, // since DisplayContest#positionStackAt() is called in TaskStack#onConfigurationChanged(), // we don't have to call WindowContainerController#positionChildAt() here. - if (stack.getWindowContainerController() != null) { - mWindowContainerController.positionChildAt(stack.getWindowContainerController(), - insertPosition, includingParents); + if (stack.getWindowContainerController() != null && mDisplayContent != null) { + mDisplayContent.positionStackAt(insertPosition, + stack.getWindowContainerController().mContainer, includingParents); } if (!wasContained) { stack.setParent(this); @@ -953,21 +956,28 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } @Override - public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { - final int currRotation = getOverrideConfiguration().windowConfiguration.getRotation(); + public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) { + final int currRotation = + getRequestedOverrideConfiguration().windowConfiguration.getRotation(); if (currRotation != ROTATION_UNDEFINED && currRotation != overrideConfiguration.windowConfiguration.getRotation() - && getWindowContainerController() != null) { - getWindowContainerController().applyRotation(currRotation, + && mDisplayContent != null) { + mDisplayContent.applyRotationLocked(currRotation, overrideConfiguration.windowConfiguration.getRotation()); } - super.onOverrideConfigurationChanged(overrideConfiguration); + super.onRequestedOverrideConfigurationChanged(overrideConfiguration); + if (mDisplayContent != null) { + mService.mWindowManager.setNewDisplayOverrideConfiguration( + overrideConfiguration, mDisplayContent); + } } @Override public void onConfigurationChanged(Configuration newParentConfig) { // update resources before cascade so that docked/pinned stacks use the correct info - getWindowContainerController().preOnConfigurationChanged(); + if (mDisplayContent != null) { + mDisplayContent.preOnConfigurationChanged(); + } super.onConfigurationChanged(newParentConfig); } @@ -1098,8 +1108,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> private void releaseSelfIfNeeded() { if (mStacks.isEmpty() && mRemoved) { - mWindowContainerController.removeContainer(); - mWindowContainerController = null; + mDisplayContent.removeIfPossible(); + mDisplayContent = null; mRootActivityContainer.removeChild(this); mRootActivityContainer.mStackSupervisor .getKeyguardController().onDisplayRemoved(mDisplayId); @@ -1121,7 +1131,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS */ boolean supportsSystemDecorations() { - return mWindowContainerController.supportsSystemDecorations(); + return mDisplayContent.supportsSystemDecorations(); } @VisibleForTesting @@ -1135,7 +1145,30 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } void setFocusedApp(ActivityRecord r, boolean moveFocusNow) { - mWindowContainerController.setFocusedApp(r.appToken, moveFocusNow); + if (mDisplayContent == null) { + return; + } + final AppWindowToken newFocus; + final IBinder token = r.appToken; + if (token == null) { + if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Clearing focused app, displayId=" + + mDisplayId); + newFocus = null; + } else { + newFocus = mService.mWindowManager.mRoot.getAppWindowToken(token); + if (newFocus == null) { + Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token + + ", displayId=" + mDisplayId); + } + if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Set focused app to: " + newFocus + + " moveFocusNow=" + moveFocusNow + " displayId=" + mDisplayId); + } + + final boolean changed = mDisplayContent.setFocusedApp(newFocus); + if (moveFocusNow && changed) { + mService.mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, + true /*updateInputWindows*/); + } } /** @@ -1283,17 +1316,21 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } /** - * See {@link DisplayWindowController#deferUpdateImeTarget()} + * See {@link DisplayContent#deferUpdateImeTarget()} */ public void deferUpdateImeTarget() { - mWindowContainerController.deferUpdateImeTarget(); + if (mDisplayContent != null) { + mDisplayContent.deferUpdateImeTarget(); + } } /** - * See {@link DisplayWindowController#deferUpdateImeTarget()} + * See {@link DisplayContent#deferUpdateImeTarget()} */ public void continueUpdateImeTarget() { - mWindowContainerController.continueUpdateImeTarget(); + if (mDisplayContent != null) { + mDisplayContent.continueUpdateImeTarget(); + } } public void dump(PrintWriter pw, String prefix) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index daf4b8ba6d48..88dd3c650a96 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -96,6 +96,7 @@ import static com.android.server.am.ActivityRecordProto.TRANSLUCENT; import static com.android.server.am.ActivityRecordProto.VISIBLE; import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY; import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY; +import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED; import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING; import static com.android.server.wm.ActivityStack.ActivityState.PAUSED; import static com.android.server.wm.ActivityStack.ActivityState.PAUSING; @@ -122,8 +123,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; -import static com.android.server.wm.ActivityTaskManagerService - .RELAUNCH_REASON_WINDOWING_MODE_RESIZE; +import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; @@ -157,6 +157,7 @@ import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.PipModeChangeItem; import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.WindowVisibilityItem; +import android.app.usage.UsageEvents.Event; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -441,8 +442,13 @@ final class ActivityRecord extends ConfigurationContainer { mLastReportedConfiguration.dump(pw, prefix + " "); pw.print(prefix); pw.print("CurrentConfiguration="); pw.println(getConfiguration()); - if (!getOverrideConfiguration().equals(EMPTY)) { - pw.println(prefix + "OverrideConfiguration=" + getOverrideConfiguration()); + if (!getRequestedOverrideConfiguration().equals(EMPTY)) { + pw.println(prefix + "RequestedOverrideConfiguration=" + + getRequestedOverrideConfiguration()); + } + if (!getResolvedOverrideConfiguration().equals(getRequestedOverrideConfiguration())) { + pw.println(prefix + "ResolvedOverrideConfiguration=" + + getResolvedOverrideConfiguration()); } if (!matchParentBounds()) { pw.println(prefix + "bounds=" + getBounds()); @@ -1030,8 +1036,6 @@ final class ActivityRecord extends ConfigurationContainer { inHistory = true; - final TaskWindowContainerController taskController = task.getWindowContainerController(); - // TODO(b/36505427): Maybe this call should be moved inside updateOverrideConfiguration() task.updateOverrideConfigurationFromLaunchBounds(); // Make sure override configuration is up-to-date before using to create window controller. @@ -1043,10 +1047,9 @@ final class ActivityRecord extends ConfigurationContainer { // TODO: Should this throw an exception instead? Slog.w(TAG, "Attempted to add existing app token: " + appToken); } else { - final Task container = taskController.mContainer; + final Task container = task.getTask(); if (container == null) { - throw new IllegalArgumentException("AppWindowContainerController: invalid " - + " controller=" + taskController); + throw new IllegalArgumentException("createAppWindowToken: invalid task =" + task); } mAppWindowToken = createAppWindow(mAtmService.mWindowManager, appToken, task.voiceSession != null, container.getDisplayContent(), @@ -1057,7 +1060,7 @@ final class ActivityRecord extends ConfigurationContainer { mLaunchTaskBehind, isAlwaysFocusable()); if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) { Slog.v(TAG, "addAppToken: " - + mAppWindowToken + " controller=" + taskController + " at " + + mAppWindowToken + " task=" + container + " at " + Integer.MAX_VALUE); } container.addChild(mAppWindowToken, Integer.MAX_VALUE /* add on top */); @@ -1143,7 +1146,7 @@ final class ActivityRecord extends ConfigurationContainer { + " r=" + this + " (" + prevTask.getStackId() + ")"); } - mAppWindowToken.reparent(newTask.getWindowContainerController(), position); + mAppWindowToken.reparent(newTask.getTask(), position); // Reparenting prevents informing the parent stack of activity removal in the case that // the new stack has the same parent. we must manually signal here if this is not the case. @@ -1796,6 +1799,18 @@ final class ActivityRecord extends ConfigurationContainer { } mAppWindowToken.detachChildren(); } + + if (state == RESUMED) { + mAtmService.updateBatteryStats(this, true); + mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_RESUMED); + } else if (state == PAUSED) { + mAtmService.updateBatteryStats(this, false); + mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_PAUSED); + } else if (state == STOPPED) { + mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_STOPPED); + } else if (state == DESTROYED) { + mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_DESTROYED); + } } ActivityState getState() { @@ -2531,13 +2546,13 @@ final class ActivityRecord extends ConfigurationContainer { mTmpConfig.unset(); computeBounds(mTmpBounds); - if (mTmpBounds.equals(getOverrideBounds())) { + if (mTmpBounds.equals(getRequestedOverrideBounds())) { return; } setBounds(mTmpBounds); - final Rect updatedBounds = getOverrideBounds(); + final Rect updatedBounds = getRequestedOverrideBounds(); // Bounds changed...update configuration to match. if (!matchParentBounds()) { @@ -2545,7 +2560,7 @@ final class ActivityRecord extends ConfigurationContainer { false /* overrideWidth */, false /* overrideHeight */); } - onOverrideConfigurationChanged(mTmpConfig); + onRequestedOverrideConfigurationChanged(mTmpConfig); } /** Returns true if the configuration is compatible with this activity. */ @@ -2602,11 +2617,11 @@ final class ActivityRecord extends ConfigurationContainer { if (containingAppWidth <= maxActivityWidth && containingAppHeight <= maxActivityHeight) { // The display matches or is less than the activity aspect ratio, so nothing else to do. // Return the existing bounds. If this method is running for the first time, - // {@link #getOverrideBounds()} will be empty (representing no override). If the method has run - // before, then effect of {@link #getOverrideBounds()} will already have been applied to the - // value returned from {@link getConfiguration}. Refer to - // {@link TaskRecord#computeOverrideConfiguration}. - outBounds.set(getOverrideBounds()); + // {@link #getRequestedOverrideBounds()} will be empty (representing no override). If + // the method has run before, then effect of {@link #getRequestedOverrideBounds()} will + // already have been applied to the value returned from {@link getConfiguration}. Refer + // to {@link TaskRecord#computeOverrideConfiguration}. + outBounds.set(getRequestedOverrideBounds()); return; } @@ -2769,7 +2784,7 @@ final class ActivityRecord extends ConfigurationContainer { final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged()); if (hasResizeChange) { final boolean isDragResizing = - getTaskRecord().getWindowContainerController().isDragResizing(); + getTaskRecord().getTask().isDragResizing(); mRelaunchReason = isDragResizing ? RELAUNCH_REASON_FREE_RESIZE : RELAUNCH_REASON_WINDOWING_MODE_RESIZE; } else { @@ -2937,7 +2952,7 @@ final class ActivityRecord extends ConfigurationContainer { final ActivityLifecycleItem lifecycleItem; if (andResume) { lifecycleItem = ResumeActivityItem.obtain( - getDisplay().getWindowContainerController().isNextTransitionForward()); + getDisplay().mDisplayContent.isNextTransitionForward()); } else { lifecycleItem = PauseActivityItem.obtain(); } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 3ccede02c430..1a6ae3e1aa7c 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -264,8 +264,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // If we created a docked stack we want to resize it so it resizes all other stacks // in the system. getStackDockedModeBounds(null, null, mTmpRect2, mTmpRect3); - mStackSupervisor.resizeDockedStackLocked( - getOverrideBounds(), mTmpRect2, mTmpRect2, null, null, PRESERVE_WINDOWS); + mStackSupervisor.resizeDockedStackLocked(getRequestedOverrideBounds(), mTmpRect2, + mTmpRect2, null, null, PRESERVE_WINDOWS); } mRootActivityContainer.updateUIDsPresentOnDisplay(); } @@ -562,7 +562,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // Update bounds if applicable boolean hasNewOverrideBounds = false; // Use override windowing mode to prevent extra bounds changes if inheriting the mode. - if (getOverrideWindowingMode() == WINDOWING_MODE_PINNED) { + if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_PINNED) { // Pinned calculation already includes rotation mTmpRect2.set(mTmpRect); hasNewOverrideBounds = getWindowContainerController().mContainer @@ -574,7 +574,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // bounds were on the pre-rotated display. if (prevRotation != newRotation) { mTmpRect2.set(mTmpRect); - getDisplay().getWindowContainerController().mContainer + getDisplay().mDisplayContent .rotateBounds(newParentConfig.windowConfiguration.getBounds(), prevRotation, newRotation, mTmpRect2); hasNewOverrideBounds = true; @@ -589,8 +589,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai || prevScreenH != getConfiguration().screenHeightDp) { // Use override windowing mode to prevent extra bounds changes if inheriting // the mode. - if (getOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || getOverrideWindowingMode() + if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + || getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { mTmpRect2.set(mTmpRect); getWindowContainerController().mContainer @@ -602,14 +602,15 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } if (getWindowingMode() != prevWindowingMode) { // Use override windowing mode to prevent extra bounds changes if inheriting the mode. - if (getOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { getStackDockedModeBounds(null, null, mTmpRect2, mTmpRect3); // immediately resize so docked bounds are available in onSplitScreenModeActivated resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */); - } else if (getOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { + } else if ( + getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { Rect dockedBounds = display.getSplitScreenPrimaryStack().getBounds(); - final boolean isMinimizedDock = getDisplay().getWindowContainerController() - .mContainer.getDockedDividerController().isMinimizedDock(); + final boolean isMinimizedDock = + getDisplay().mDisplayContent.getDockedDividerController().isMinimizedDock(); if (isMinimizedDock) { TaskRecord topTask = display.getSplitScreenPrimaryStack().topTask(); if (topTask != null) { @@ -672,7 +673,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai void setWindowingMode(int preferredWindowingMode, boolean animate, boolean showRecents, boolean enteringSplitScreenMode, boolean deferEnsuringVisibility, boolean creating) { final int currentMode = getWindowingMode(); - final int currentOverrideMode = getOverrideWindowingMode(); + final int currentOverrideMode = getRequestedOverrideWindowingMode(); final ActivityDisplay display = getDisplay(); final TaskRecord topTask = topTask(); final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack(); @@ -725,7 +726,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // You are already in the window mode, so we can skip most of the work below. However, // it's possible that we have inherited the current windowing mode from a parent. So, // fulfill this method's contract by setting the override mode directly. - getOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); + getRequestedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode); return; } @@ -791,7 +792,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mWindowContainerController.getRawBounds(mTmpRect2); } - if (!Objects.equals(getOverrideBounds(), mTmpRect2)) { + if (!Objects.equals(getRequestedOverrideBounds(), mTmpRect2)) { resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */); } } finally { @@ -910,7 +911,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } void positionChildWindowContainerAtTop(TaskRecord child) { - mWindowContainerController.positionChildAtTop(child.getWindowContainerController(), + mWindowContainerController.positionChildAtTop(child.getTask(), true /* includingParents */); } @@ -920,7 +921,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // task to bottom, the next focusable stack on the same display should be focused. final ActivityStack nextFocusableStack = getDisplay().getNextFocusableStack( child.getStack(), true /* ignoreCurrent */); - mWindowContainerController.positionChildAtBottom(child.getWindowContainerController(), + mWindowContainerController.positionChildAtBottom(child.getTask(), nextFocusableStack == null /* includingParents */); } @@ -1616,7 +1617,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai try { EventLogTags.writeAmPauseActivity(prev.mUserId, System.identityHashCode(prev), prev.shortComponentName, "userLeaving=" + userLeaving); - mService.updateUsageStats(prev, false); mService.getLifecycleManager().scheduleTransaction(prev.app.getThread(), prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving, @@ -1796,7 +1796,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // focus). Also if there is an active pinned stack - we always want to notify it about // task stack changes, because its positioning may depend on it. if (mStackSupervisor.mAppVisibilitiesChangedSinceLastPause - || getDisplay().hasPinnedStack()) { + || (getDisplay() != null && getDisplay().hasPinnedStack())) { mService.getTaskChangeNotificationController().notifyTaskStackChanged(); mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = false; } @@ -2713,16 +2713,16 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // that the previous one will be hidden soon. This way it can know // to ignore it when computing the desired screen orientation. boolean anim = true; - final DisplayWindowController dwc = getDisplay().getWindowContainerController(); + final DisplayContent dc = getDisplay().mDisplayContent; if (prev != null) { if (prev.finishing) { if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev); if (mStackSupervisor.mNoAnimActivities.contains(prev)) { anim = false; - dwc.prepareAppTransition(TRANSIT_NONE, false); + dc.prepareAppTransition(TRANSIT_NONE, false); } else { - dwc.prepareAppTransition( + dc.prepareAppTransition( prev.getTaskRecord() == next.getTaskRecord() ? TRANSIT_ACTIVITY_CLOSE : TRANSIT_TASK_CLOSE, false); } @@ -2732,9 +2732,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai "Prepare open transition: prev=" + prev); if (mStackSupervisor.mNoAnimActivities.contains(next)) { anim = false; - dwc.prepareAppTransition(TRANSIT_NONE, false); + dc.prepareAppTransition(TRANSIT_NONE, false); } else { - dwc.prepareAppTransition( + dc.prepareAppTransition( prev.getTaskRecord() == next.getTaskRecord() ? TRANSIT_ACTIVITY_OPEN : next.mLaunchTaskBehind ? TRANSIT_TASK_OPEN_BEHIND : TRANSIT_TASK_OPEN, false); @@ -2744,9 +2744,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous"); if (mStackSupervisor.mNoAnimActivities.contains(next)) { anim = false; - dwc.prepareAppTransition(TRANSIT_NONE, false); + dc.prepareAppTransition(TRANSIT_NONE, false); } else { - dwc.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false); + dc.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false); } } @@ -2868,8 +2868,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai next.clearOptionsLocked(); transaction.setLifecycleStateRequest( ResumeActivityItem.obtain(next.app.getReportedProcState(), - getDisplay().getWindowContainerController() - .isNextTransitionForward())); + getDisplay().mDisplayContent.isNextTransitionForward())); mService.getLifecycleManager().scheduleTransaction(transaction); if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " @@ -2984,7 +2983,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai position = getAdjustedPositionForTask(task, position, null /* starting */); mTaskHistory.remove(task); mTaskHistory.add(position, task); - mWindowContainerController.positionChildAt(task.getWindowContainerController(), position); + mWindowContainerController.positionChildAt(task.getTask(), position); updateTaskMovement(task, true); } @@ -3071,11 +3070,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai task.setFrontOfTask(); if (!isHomeOrRecentsStack() || numActivities() > 0) { - final DisplayWindowController dwc = getDisplay().getWindowContainerController(); + final DisplayContent dc = getDisplay().mDisplayContent; if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: starting " + r); if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - dwc.prepareAppTransition(TRANSIT_NONE, keepCurTransition); + dc.prepareAppTransition(TRANSIT_NONE, keepCurTransition); mStackSupervisor.mNoAnimActivities.add(r); } else { int transit = TRANSIT_ACTIVITY_OPEN; @@ -3094,7 +3093,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai transit = TRANSIT_TASK_OPEN; } } - dwc.prepareAppTransition(transit, keepCurTransition); + dc.prepareAppTransition(transit, keepCurTransition); mStackSupervisor.mNoAnimActivities.remove(r); } boolean doShow = true; @@ -3723,7 +3722,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai int taskNdx = mTaskHistory.indexOf(finishedTask); final TaskRecord task = finishedTask; int activityNdx = task.mActivities.indexOf(r); - getDisplay().getWindowContainerController().prepareAppTransition( + getDisplay().mDisplayContent.prepareAppTransition( TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */); finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, false); finishedTask = task; @@ -3889,7 +3888,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mService.getTaskChangeNotificationController().notifyTaskRemovalStarted( task.taskId); } - getDisplay().getWindowContainerController().prepareAppTransition(transit, false); + getDisplay().mDisplayContent.prepareAppTransition(transit, false); // Tell window manager to prepare for this one to be removed. r.setVisibility(false); @@ -3944,10 +3943,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } private void prepareActivityHideTransitionAnimation(ActivityRecord r, int transit) { - final DisplayWindowController dwc = getDisplay().getWindowContainerController(); - dwc.prepareAppTransition(transit, false); + final DisplayContent dc = getDisplay().mDisplayContent; + dc.prepareAppTransition(transit, false); r.setVisibility(false); - dwc.executeAppTransition(); + dc.executeAppTransition(); if (!mStackSupervisor.mActivitiesWaitingForVisibleActivity.contains(r)) { mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(r); } @@ -4649,9 +4648,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai r.mUserId, System.identityHashCode(r), r.getTaskRecord().taskId, r.shortComponentName, "proc died without state saved"); - if (r.getState() == RESUMED) { - mService.updateUsageStats(r, false); - } } } else { // We have the current state for this activity, so @@ -4691,7 +4687,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai ActivityOptions.abort(options); } } - getDisplay().getWindowContainerController().prepareAppTransition(transit, false); + getDisplay().mDisplayContent.prepareAppTransition(transit, false); } private void updateTaskMovement(TaskRecord task, boolean toFront) { @@ -4760,8 +4756,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr); if (noAnimation) { - getDisplay().getWindowContainerController().prepareAppTransition( - TRANSIT_NONE, false); + getDisplay().mDisplayContent.prepareAppTransition(TRANSIT_NONE, false); if (r != null) { mStackSupervisor.mNoAnimActivities.add(r); } @@ -4843,8 +4838,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mTaskHistory.add(0, tr); updateTaskMovement(tr, false); - getDisplay().getWindowContainerController().prepareAppTransition( - TRANSIT_TASK_TO_BACK, false); + getDisplay().mDisplayContent.prepareAppTransition(TRANSIT_TASK_TO_BACK, false); moveToBack("moveTaskToBackLocked", tr); if (inPinnedWindowingMode()) { @@ -4934,8 +4928,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // For freeform stack we don't adjust the size of the tasks to match that // of the stack, but we do try to make sure the tasks are still contained // with the bounds of the stack. - if (task.getOverrideBounds() != null) { - mTmpRect2.set(task.getOverrideBounds()); + if (task.getRequestedOverrideBounds() != null) { + mTmpRect2.set(task.getRequestedOverrideBounds()); fitWithinBounds(mTmpRect2, bounds); task.updateOverrideConfiguration(mTmpRect2); } @@ -4946,9 +4940,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (task.hasDisplayedBounds()) { mTmpBounds.put(task.taskId, task.getDisplayedBounds()); - mTmpInsetBounds.put(task.taskId, task.getOverrideBounds()); + mTmpInsetBounds.put(task.taskId, task.getRequestedOverrideBounds()); } else { - mTmpBounds.put(task.taskId, task.getOverrideBounds()); + mTmpBounds.put(task.taskId, task.getRequestedOverrideBounds()); mTmpInsetBounds.put(task.taskId, null); } } @@ -5169,7 +5163,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai + r.intent.getComponent().flattenToShortString()); // Force the destroy to skip right to removal. r.app = null; - getDisplay().getWindowContainerController().prepareAppTransition( + getDisplay().mDisplayContent.prepareAppTransition( TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */); finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false, "handleAppCrashedLocked"); @@ -5191,7 +5185,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai pw.println(""); } pw.println(prefix + "Task id #" + task.taskId); - pw.println(prefix + "mBounds=" + task.getOverrideBounds()); + pw.println(prefix + "mBounds=" + task.getRequestedOverrideBounds()); pw.println(prefix + "mMinWidth=" + task.mMinWidth); pw.println(prefix + "mMinHeight=" + task.mMinHeight); pw.println(prefix + "mLastNonFullscreenBounds=" + task.mLastNonFullscreenBounds); @@ -5349,9 +5343,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (!mStackSupervisor.getLaunchParamsController() .layoutTask(task, info.windowLayout, activity, source, options) && !matchParentBounds() && task.isResizeable() && !isLockscreenShown) { - task.updateOverrideConfiguration(getOverrideBounds()); + task.updateOverrideConfiguration(getRequestedOverrideBounds()); } - task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0); + task.createTask(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0); return task; } @@ -5507,7 +5501,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } void executeAppTransition(ActivityOptions options) { - getDisplay().getWindowContainerController().executeAppTransition(); + getDisplay().mDisplayContent.executeAppTransition(); ActivityOptions.abort(options); } @@ -5541,7 +5535,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } proto.write(DISPLAY_ID, mDisplayId); if (!matchParentBounds()) { - final Rect bounds = getOverrideBounds(); + final Rect bounds = getRequestedOverrideBounds(); bounds.writeToProto(proto, BOUNDS); } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index f3c5630b53f0..4339e5138d1b 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -818,7 +818,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final ClientTransaction clientTransaction = ClientTransaction.obtain( proc.getThread(), r.appToken); - final DisplayWindowController dwc = r.getDisplay().getWindowContainerController(); + final DisplayContent dc = r.getDisplay().mDisplayContent; clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent), System.identityHashCode(r), r.info, // TODO: Have this take the merged configuration instead of separate global @@ -827,12 +827,12 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mergedConfiguration.getOverrideConfiguration(), r.compat, r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(), r.icicle, r.persistentState, results, newIntents, - dwc.isNextTransitionForward(), profilerInfo)); + dc.isNextTransitionForward(), profilerInfo)); // Set desired final state. final ActivityLifecycleItem lifecycleItem; if (andResume) { - lifecycleItem = ResumeActivityItem.obtain(dwc.isNextTransitionForward()); + lifecycleItem = ResumeActivityItem.obtain(dc.isNextTransitionForward()); } else { lifecycleItem = PauseActivityItem.obtain(); } @@ -1868,7 +1868,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { stack.addTask(task, onTop, "restoreRecentTask"); // TODO: move call for creation here and other place into Stack.addTask() - task.createWindowContainer(onTop, true /* showForAllUsers */); + task.createTask(onTop, true /* showForAllUsers */); if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Added restored task=" + task + " to stack=" + stack); final ArrayList<ActivityRecord> activities = task.mActivities; @@ -2040,9 +2040,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mStoppingActivities.remove(r); final ActivityStack stack = r.getActivityStack(); - if (mRootActivityContainer.isTopDisplayFocusedStack(stack)) { - mService.updateUsageStats(r, true); - } if (stack.getDisplay().allResumedActivitiesComplete()) { mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); // Make sure activity & window visibility should be identical @@ -2410,7 +2407,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { return; } - scheduleUpdatePictureInPictureModeIfNeeded(task, stack.getOverrideBounds()); + scheduleUpdatePictureInPictureModeIfNeeded(task, stack.getRequestedOverrideBounds()); } void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index bc2136ebcf15..57bfc2979636 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1565,7 +1565,7 @@ class ActivityStarter { mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); // Go ahead and tell window manager to execute app transition for this activity // since the app transition will not be triggered through the resume channel. - mTargetStack.getDisplay().getWindowContainerController().executeAppTransition(); + mTargetStack.getDisplay().mDisplayContent.executeAppTransition(); } else { // If the target stack was not previously focusable (previous top running activity // on that stack was not visible) then any prior calls to move the stack to the @@ -2506,8 +2506,9 @@ class ActivityStarter { // full resolution. mLaunchParams.mPreferredDisplayId = mPreferredDisplayId != DEFAULT_DISPLAY ? mPreferredDisplayId : INVALID_DISPLAY; + final boolean onTop = aOptions == null || !aOptions.getAvoidMoveToFront(); final ActivityStack stack = - mRootActivityContainer.getLaunchStack(r, aOptions, task, ON_TOP, mLaunchParams); + mRootActivityContainer.getLaunchStack(r, aOptions, task, onTop, mLaunchParams); mLaunchParams.mPreferredDisplayId = mPreferredDisplayId; return stack; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 61eb9d4b8abf..46ee08f740b0 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1738,7 +1738,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (self.isState( ActivityStack.ActivityState.RESUMED, ActivityStack.ActivityState.PAUSING)) { - self.getDisplay().getWindowContainerController().overridePendingAppTransition( + self.getDisplay().mDisplayContent.mAppTransition.overridePendingAppTransition( packageName, enterAnim, exitAnim, null); } @@ -3073,12 +3073,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // Get top display of front most application. final ActivityStack focusedStack = getTopDisplayFocusedStack(); if (focusedStack != null) { - final DisplayWindowController dwc = - focusedStack.getDisplay().getWindowContainerController(); - dwc.prepareAppTransition(TRANSIT_TASK_IN_PLACE, false); - dwc.overridePendingAppTransitionInPlace(activityOptions.getPackageName(), + final DisplayContent dc = focusedStack.getDisplay().mDisplayContent; + dc.prepareAppTransition(TRANSIT_TASK_IN_PLACE, false); + dc.mAppTransition.overrideInPlaceAppTransition(activityOptions.getPackageName(), activityOptions.getCustomInPlaceResId()); - dwc.executeAppTransition(); + dc.executeAppTransition(); } } @@ -5235,13 +5234,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mH.post(mAmInternal::updateCpuStats); } - void updateUsageStats(ActivityRecord component, boolean resumed) { - final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::updateUsageStats, + void updateBatteryStats(ActivityRecord component, boolean resumed) { + final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::updateBatteryStats, mAmInternal, component.mActivityComponent, component.app.mUid, component.mUserId, resumed); mH.sendMessage(m); } + void updateActivityUsageStats(ActivityRecord activity, int event) { + final Message m = PooledLambda.obtainMessage( + ActivityManagerInternal::updateActivityUsageStats, mAmInternal, + activity.mActivityComponent, activity.mUserId, event, activity.appToken); + mH.sendMessage(m); + } + void setBooting(boolean booting) { mAmInternal.setBooting(booting); } @@ -5770,17 +5776,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (activityDisplay == null) { return; } - final DisplayWindowController dwc = activityDisplay.getWindowContainerController(); - final boolean wasTransitionSet = dwc.getPendingAppTransition() != TRANSIT_NONE; + final DisplayContent dc = activityDisplay.mDisplayContent; + final boolean wasTransitionSet = + dc.mAppTransition.getAppTransition() != TRANSIT_NONE; if (!wasTransitionSet) { - dwc.prepareAppTransition(TRANSIT_NONE, false /* alwaysKeepCurrent */); + dc.prepareAppTransition(TRANSIT_NONE, false /* alwaysKeepCurrent */); } mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); // If there was a transition set already we don't want to interfere with it as we // might be starting it too early. if (!wasTransitionSet) { - dwc.executeAppTransition(); + dc.executeAppTransition(); } } if (callback != null) { @@ -6270,18 +6277,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { finishInstrumentationCallback.run(); } - mWindowManager.deferSurfaceLayout(); - try { - if (!restarting && hasVisibleActivities - && !mRootActivityContainer.resumeFocusedStacksTopActivities()) { - // If there was nothing to resume, and we are not already restarting this - // process, but there is a visible activity that is hosted by the process... - // then make sure all visible activities are running, taking care of - // restarting this process. - mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); + if (!restarting && hasVisibleActivities) { + mWindowManager.deferSurfaceLayout(); + try { + if (!mRootActivityContainer.resumeFocusedStacksTopActivities()) { + // If there was nothing to resume, and we are not already restarting + // this process, but there is a visible activity that is hosted by the + // process...then make sure all visible activities are running, taking + // care of restarting this process. + mRootActivityContainer.ensureActivitiesVisible(null, 0, + !PRESERVE_WINDOWS); + } + } finally { + mWindowManager.continueSurfaceLayout(); } - } finally { - mWindowManager.continueSurfaceLayout(); } } } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index c458c94b59e2..e0d3fbed4587 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -1175,21 +1175,14 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } } - void reparent(TaskWindowContainerController taskController, int position) { + void reparent(Task task, int position) { if (DEBUG_ADD_REMOVE) { Slog.i(TAG_WM, "reparent: moving app token=" + this - + " to task=" + taskController + " at " + position); + + " to task=" + task.mTaskId + " at " + position); } - final Task task = taskController.mContainer; if (task == null) { - throw new IllegalArgumentException("reparent: could not find task=" - + taskController); + throw new IllegalArgumentException("reparent: could not find task"); } - reparent(task, position); - getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); - } - - void reparent(Task task, int position) { final Task currentTask = getTask(); if (task == currentTask) { throw new IllegalArgumentException( @@ -1220,6 +1213,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree onDisplayChanged(displayContent); prevDisplayContent.setLayoutNeeded(); } + getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); } @Override diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java index a335fa26dfcf..5b20af3534c4 100644 --- a/services/core/java/com/android/server/wm/BarController.java +++ b/services/core/java/com/android/server/wm/BarController.java @@ -219,7 +219,7 @@ public class BarController { } private boolean updateStateLw(final int state) { - if (state != mState) { + if (mWin != null && state != mState) { mState = state; if (DEBUG) Slog.d(mTag, "mState: " + StatusBarManager.windowStateToString(state)); mHandler.post(new Runnable() { diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index e779d1bb20f5..ded45c9e3079 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -49,20 +49,28 @@ import java.util.ArrayList; */ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { /** - * {@link #Rect} returned from {@link #getOverrideBounds()} to prevent original value from being - * set directly. + * {@link #Rect} returned from {@link #getRequestedOverrideBounds()} to prevent original value + * from being set directly. */ private Rect mReturnBounds = new Rect(); - /** Contains override configuration settings applied to this configuration container. */ - private Configuration mOverrideConfiguration = new Configuration(); + /** + * Contains requested override configuration settings applied to this configuration container. + */ + private Configuration mRequestedOverrideConfiguration = new Configuration(); + + /** + * Contains the requested override configuration with parent and policy constraints applied. + * This is the set of overrides that gets applied to the full and merged configurations. + */ + private Configuration mResolvedOverrideConfiguration = new Configuration(); - /** True if mOverrideConfiguration is not empty */ + /** True if mRequestedOverrideConfiguration is not empty */ private boolean mHasOverrideConfiguration; /** * Contains full configuration applied to this configuration container. Corresponds to full - * parent's config with applied {@link #mOverrideConfiguration}. + * parent's config with applied {@link #mResolvedOverrideConfiguration}. */ private Configuration mFullConfiguration = new Configuration(); @@ -102,41 +110,62 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { * @see #mFullConfiguration */ public void onConfigurationChanged(Configuration newParentConfig) { + mTmpConfig.setTo(mResolvedOverrideConfiguration); + resolveOverrideConfiguration(newParentConfig); mFullConfiguration.setTo(newParentConfig); - mFullConfiguration.updateFrom(mOverrideConfiguration); + mFullConfiguration.updateFrom(mResolvedOverrideConfiguration); + if (!mTmpConfig.equals(mResolvedOverrideConfiguration)) { + onMergedOverrideConfigurationChanged(); + // This depends on the assumption that change-listeners don't do + // their own override resolution. This way, dependent hierarchies + // can stay properly synced-up with a primary hierarchy's constraints. + // Since the hierarchies will be merged, this whole thing will go away + // before the assumption will be broken. + // Inform listeners of the change. + for (int i = mChangeListeners.size() - 1; i >= 0; --i) { + mChangeListeners.get(i).onRequestedOverrideConfigurationChanged( + mResolvedOverrideConfiguration); + } + } for (int i = getChildCount() - 1; i >= 0; --i) { final ConfigurationContainer child = getChildAt(i); child.onConfigurationChanged(mFullConfiguration); } } - /** Returns override configuration applied to this configuration container. */ - public Configuration getOverrideConfiguration() { - return mOverrideConfiguration; + /** + * Resolves the current requested override configuration into + * {@link #mResolvedOverrideConfiguration} + * + * @param newParentConfig The new parent configuration to resolve overrides against. + */ + void resolveOverrideConfiguration(Configuration newParentConfig) { + mResolvedOverrideConfiguration.setTo(mRequestedOverrideConfiguration); + } + + /** Returns requested override configuration applied to this configuration container. */ + public Configuration getRequestedOverrideConfiguration() { + return mRequestedOverrideConfiguration; + } + + /** Returns the resolved override configuration. */ + Configuration getResolvedOverrideConfiguration() { + return mResolvedOverrideConfiguration; } /** * Update override configuration and recalculate full config. - * @see #mOverrideConfiguration + * @see #mRequestedOverrideConfiguration * @see #mFullConfiguration */ - public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { + public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) { // Pre-compute this here, so we don't need to go through the entire Configuration when // writing to proto (which has significant cost if we write a lot of empty configurations). mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration); - mOverrideConfiguration.setTo(overrideConfiguration); + mRequestedOverrideConfiguration.setTo(overrideConfiguration); // Update full configuration of this container and all its children. final ConfigurationContainer parent = getParent(); onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY); - // Update merged override config of this container and all its children. - onMergedOverrideConfigurationChanged(); - - // Use the updated override configuration to notify listeners. - mTmpConfig.setTo(mOverrideConfiguration); - // Inform listeners of the change. - for (int i = mChangeListeners.size() - 1; i >=0; --i) { - mChangeListeners.get(i).onOverrideConfigurationChanged(mTmpConfig); - } } /** @@ -157,9 +186,9 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { final ConfigurationContainer parent = getParent(); if (parent != null) { mMergedOverrideConfiguration.setTo(parent.getMergedOverrideConfiguration()); - mMergedOverrideConfiguration.updateFrom(mOverrideConfiguration); + mMergedOverrideConfiguration.updateFrom(mResolvedOverrideConfiguration); } else { - mMergedOverrideConfiguration.setTo(mOverrideConfiguration); + mMergedOverrideConfiguration.setTo(mResolvedOverrideConfiguration); } for (int i = getChildCount() - 1; i >= 0; --i) { final ConfigurationContainer child = getChildAt(i); @@ -168,24 +197,26 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } /** - * Indicates whether this container has not specified any bounds different from its parent. In - * this case, it will inherit the bounds of the first ancestor which specifies a bounds. - * @return {@code true} if no explicit bounds have been set at this container level. + * Indicates whether this container has not requested any bounds different from its parent. In + * this case, it will inherit the bounds of the first ancestor which specifies a bounds subject + * to policy constraints. + * + * @return {@code true} if no explicit bounds have been requested at this container level. * {@code false} otherwise. */ public boolean matchParentBounds() { - return getOverrideBounds().isEmpty(); + return getRequestedOverrideBounds().isEmpty(); } /** - * Returns whether the bounds specified is considered the same as the existing override bounds. - * This is either when the two bounds are equal or the override bounds is empty and the - * specified bounds is null. + * Returns whether the bounds specified are considered the same as the existing requested + * override bounds. This is either when the two bounds are equal or the requested override + * bounds are empty and the specified bounds is null. * * @return {@code true} if the bounds are equivalent, {@code false} otherwise */ - public boolean equivalentOverrideBounds(Rect bounds) { - return equivalentBounds(getOverrideBounds(), bounds); + public boolean equivalentRequestedOverrideBounds(Rect bounds) { + return equivalentBounds(getRequestedOverrideBounds(), bounds); } /** @@ -212,50 +243,52 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } /** - * Returns the current bounds explicitly set on this container. The {@link Rect} handed back is + * Returns the bounds requested on this container. These may not be the actual bounds the + * container ends up with due to policy constraints. The {@link Rect} handed back is * shared for all calls to this method and should not be modified. */ - public Rect getOverrideBounds() { - mReturnBounds.set(getOverrideConfiguration().windowConfiguration.getBounds()); + public Rect getRequestedOverrideBounds() { + mReturnBounds.set(getRequestedOverrideConfiguration().windowConfiguration.getBounds()); return mReturnBounds; } /** - * Returns {@code true} if the {@link WindowConfiguration} in the override + * Returns {@code true} if the {@link WindowConfiguration} in the requested override * {@link Configuration} specifies bounds. */ public boolean hasOverrideBounds() { - return !getOverrideBounds().isEmpty(); + return !getRequestedOverrideBounds().isEmpty(); } /** * Sets the passed in {@link Rect} to the current bounds. - * @see {@link #getOverrideBounds()}. + * @see {@link #getRequestedOverrideBounds()}. */ - public void getOverrideBounds(Rect outBounds) { - outBounds.set(getOverrideBounds()); + public void getRequestedOverrideBounds(Rect outBounds) { + outBounds.set(getRequestedOverrideBounds()); } /** * Sets the bounds at the current hierarchy level, overriding any bounds set on an ancestor. - * This value will be reported when {@link #getBounds()} and {@link #getOverrideBounds()}. If + * This value will be reported when {@link #getBounds()} and + * {@link #getRequestedOverrideBounds()}. If * an empty {@link Rect} or null is specified, this container will be considered to match its * parent bounds {@see #matchParentBounds} and will inherit bounds from its parent. * @param bounds The bounds defining the container size. * @return a bitmask representing the types of changes made to the bounds. */ public int setBounds(Rect bounds) { - int boundsChange = diffOverrideBounds(bounds); + int boundsChange = diffRequestedOverrideBounds(bounds); if (boundsChange == BOUNDS_CHANGE_NONE) { return boundsChange; } - mTmpConfig.setTo(getOverrideConfiguration()); + mTmpConfig.setTo(getRequestedOverrideConfiguration()); mTmpConfig.windowConfiguration.setBounds(bounds); - onOverrideConfigurationChanged(mTmpConfig); + onRequestedOverrideConfigurationChanged(mTmpConfig); return boundsChange; } @@ -265,14 +298,14 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return setBounds(mTmpRect); } - int diffOverrideBounds(Rect bounds) { - if (equivalentOverrideBounds(bounds)) { + int diffRequestedOverrideBounds(Rect bounds) { + if (equivalentRequestedOverrideBounds(bounds)) { return BOUNDS_CHANGE_NONE; } int boundsChange = BOUNDS_CHANGE_NONE; - final Rect existingBounds = getOverrideBounds(); + final Rect existingBounds = getRequestedOverrideBounds(); if (bounds == null || existingBounds.left != bounds.left || existingBounds.top != bounds.top) { @@ -296,15 +329,16 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return mFullConfiguration.windowConfiguration.getWindowingMode(); } - public int getOverrideWindowingMode() { - return mOverrideConfiguration.windowConfiguration.getWindowingMode(); + /** Returns the windowing mode override that is requested by this container. */ + public int getRequestedOverrideWindowingMode() { + return mRequestedOverrideConfiguration.windowConfiguration.getWindowingMode(); } - /** Sets the windowing mode for the configuration container. */ + /** Sets the requested windowing mode override for the configuration container. */ public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) { - mTmpConfig.setTo(getOverrideConfiguration()); + mTmpConfig.setTo(getRequestedOverrideConfiguration()); mTmpConfig.windowConfiguration.setWindowingMode(windowingMode); - onOverrideConfigurationChanged(mTmpConfig); + onRequestedOverrideConfigurationChanged(mTmpConfig); } /** Sets the always on top flag for this configuration container. @@ -314,16 +348,16 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { * - {@Link ActivityDisplay#positionChildAtTop(ActivityStack)}; * */ public void setAlwaysOnTop(boolean alwaysOnTop) { - mTmpConfig.setTo(getOverrideConfiguration()); + mTmpConfig.setTo(getRequestedOverrideConfiguration()); mTmpConfig.windowConfiguration.setAlwaysOnTop(alwaysOnTop); - onOverrideConfigurationChanged(mTmpConfig); + onRequestedOverrideConfigurationChanged(mTmpConfig); } /** Sets the windowing mode for the configuration container. */ void setDisplayWindowingMode(int windowingMode) { - mTmpConfig.setTo(getOverrideConfiguration()); + mTmpConfig.setTo(getRequestedOverrideConfiguration()); mTmpConfig.windowConfiguration.setDisplayWindowingMode(windowingMode); - onOverrideConfigurationChanged(mTmpConfig); + onRequestedOverrideConfigurationChanged(mTmpConfig); } /** @@ -393,9 +427,9 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { throw new IllegalStateException("Can't change activity type once set: " + this + " activityType=" + activityTypeToString(activityType)); } - mTmpConfig.setTo(getOverrideConfiguration()); + mTmpConfig.setTo(getRequestedOverrideConfiguration()); mTmpConfig.windowConfiguration.setActivityType(activityType); - onOverrideConfigurationChanged(mTmpConfig); + onRequestedOverrideConfigurationChanged(mTmpConfig); } public boolean isActivityTypeHome() { @@ -472,7 +506,7 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return; } mChangeListeners.add(listener); - listener.onOverrideConfigurationChanged(mOverrideConfiguration); + listener.onRequestedOverrideConfigurationChanged(mResolvedOverrideConfiguration); } public void unregisterConfigurationChangeListener(ConfigurationContainerListener listener) { @@ -508,7 +542,7 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) { final long token = proto.start(fieldId); if (!trim || mHasOverrideConfiguration) { - mOverrideConfiguration.writeToProto(proto, OVERRIDE_CONFIGURATION); + mRequestedOverrideConfiguration.writeToProto(proto, OVERRIDE_CONFIGURATION); } if (!trim) { mFullConfiguration.writeToProto(proto, FULL_CONFIGURATION); @@ -526,7 +560,7 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { pw.println(getName() + " type=" + activityTypeToString(getActivityType()) + " mode=" + windowingModeToString(getWindowingMode()) - + " override-mode=" + windowingModeToString(getOverrideWindowingMode())); + + " override-mode=" + windowingModeToString(getRequestedOverrideWindowingMode())); for (int i = getChildCount() - 1; i >= 0; --i) { final E cc = getChildAt(i); pw.print(childPrefix + "#" + i + " "); diff --git a/services/core/java/com/android/server/wm/ConfigurationContainerListener.java b/services/core/java/com/android/server/wm/ConfigurationContainerListener.java index ff14d9784b04..dc4939d55bfa 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainerListener.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainerListener.java @@ -23,5 +23,6 @@ import android.content.res.Configuration; */ public interface ConfigurationContainerListener { - void onOverrideConfigurationChanged(Configuration overrideConfiguration); + /** {@see ConfigurationContainer#onRequestedOverrideConfigurationChanged} */ + void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index aba2eb38ce15..2f4c5cab2559 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -59,8 +59,11 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; +import static android.view.WindowManager.TRANSIT_TASK_OPEN; +import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; @@ -207,6 +210,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** Unique identifier of this stack. */ private final int mDisplayId; + // TODO: Remove once unification is complete. + ActivityDisplay mAcitvityDisplay; + /** The containers below are the only child containers the display can have. */ // Contains all window containers that are related to apps (Activities) private final TaskStackContainers mTaskStackContainers = new TaskStackContainers(mWmService); @@ -227,7 +233,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private WindowState mTmpWindow; private WindowState mTmpWindow2; - private WindowAnimator mTmpWindowAnimator; private boolean mTmpRecoveringMemory; private boolean mUpdateImeTarget; private boolean mTmpInitial; @@ -827,12 +832,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * initialize direct children. * @param display May not be null. * @param service You know. - * @param controller The controller for the display container. + * @param activityDisplay The ActivityDisplay for the display container. */ DisplayContent(Display display, WindowManagerService service, - DisplayWindowController controller) { + ActivityDisplay activityDisplay) { super(service); - setController(controller); + mAcitvityDisplay = activityDisplay; if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) { throw new IllegalArgumentException("Display with ID=" + display.getDisplayId() + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId()) @@ -1020,11 +1025,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } @Override - DisplayWindowController getController() { - return (DisplayWindowController) super.getController(); - } - - @Override public Display getDisplay() { return mDisplay; } @@ -1138,6 +1138,17 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return true; } + /** + * The display content may have configuration set from {@link #DisplayWindowSettings}. This + * callback let the owner of container know there is existing configuration to prevent the + * values from being replaced by the initializing {@link #ActivityDisplay}. + */ + void initializeDisplayOverrideConfiguration() { + if (mAcitvityDisplay != null) { + mAcitvityDisplay.onInitializeOverrideConfiguration(getRequestedOverrideConfiguration()); + } + } + /** Notify the configuration change of this display. */ void sendNewConfiguration() { mWmService.mH.obtainMessage(SEND_NEW_CONFIGURATION, this).sendToTarget(); @@ -4686,6 +4697,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } void prepareAppTransition(@WindowManager.TransitionType int transit, + boolean alwaysKeepCurrent) { + prepareAppTransition(transit, alwaysKeepCurrent, 0 /* flags */, false /* forceOverride */); + } + + void prepareAppTransition(@WindowManager.TransitionType int transit, boolean alwaysKeepCurrent, @WindowManager.TransitionFlags int flags, boolean forceOverride) { final boolean prepared = mAppTransition.prepareAppTransitionLocked( @@ -4737,6 +4753,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pendingLayoutChanges |= changes; } + /** Check if pending app transition is for activity / task launch. */ + boolean isNextTransitionForward() { + final int transit = mAppTransition.getAppTransition(); + return transit == TRANSIT_ACTIVITY_OPEN + || transit == TRANSIT_TASK_OPEN + || transit == TRANSIT_TASK_TO_FRONT; + } + /** * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS */ diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index d4bd91b008d4..8b8cadc7a4a6 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -763,6 +763,12 @@ public class DisplayPolicy { || attrs.hideTimeoutMilliseconds > TOAST_WINDOW_TIMEOUT) { attrs.hideTimeoutMilliseconds = TOAST_WINDOW_TIMEOUT; } + // Accessibility users may need longer timeout duration. This api compares + // original timeout with user's preference and return longer one. It returns + // original timeout if there's no preference. + attrs.hideTimeoutMilliseconds = mAccessibilityManager.getRecommendedTimeoutMillis( + (int) attrs.hideTimeoutMilliseconds, + AccessibilityManager.FLAG_CONTENT_TEXT); attrs.windowAnimations = com.android.internal.R.style.Animation_Toast; break; } diff --git a/services/core/java/com/android/server/wm/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java deleted file mode 100644 index 55b3def7817b..000000000000 --- a/services/core/java/com/android/server/wm/DisplayWindowController.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) 2018 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.wm; - -import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; -import static android.view.WindowManager.TRANSIT_TASK_OPEN; -import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; - -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; - -import android.content.res.Configuration; -import android.graphics.GraphicBuffer; -import android.os.Binder; -import android.os.IBinder; -import android.os.IRemoteCallback; -import android.util.Slog; -import android.view.AppTransitionAnimationSpec; -import android.view.Display; -import android.view.WindowManager; -import android.view.WindowManager.TransitionType; - -import com.android.internal.annotations.VisibleForTesting; - -/** - * Controller for the display container. This is created by activity manager to link activity - * displays to the display content they use in window manager. - */ -public class DisplayWindowController - extends WindowContainerController<DisplayContent, WindowContainerListener> { - - private final int mDisplayId; - - public DisplayWindowController(Display display, WindowContainerListener listener) { - super(listener, WindowManagerService.getInstance()); - mDisplayId = display.getDisplayId(); - - synchronized (mGlobalLock) { - final long callingIdentity = Binder.clearCallingIdentity(); - try { - mRoot.createDisplayContent(display, this /* controller */); - } finally { - Binder.restoreCallingIdentity(callingIdentity); - } - - if (mContainer == null) { - throw new IllegalArgumentException("Trying to add display=" + display - + " dc=" + mRoot.getDisplayContent(mDisplayId)); - } - } - } - - @VisibleForTesting - public DisplayWindowController(Display display, WindowManagerService service) { - super(null, service); - mDisplayId = display.getDisplayId(); - } - - @Override - public void removeContainer() { - synchronized (mGlobalLock) { - if(mContainer == null) { - if (DEBUG_DISPLAY) Slog.i(TAG_WM, "removeDisplay: could not find displayId=" - + mDisplayId); - return; - } - mContainer.removeIfPossible(); - super.removeContainer(); - } - } - - @Override - public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.mWmService.setNewDisplayOverrideConfiguration(overrideConfiguration, - mContainer); - } - } - } - - /** - * Updates the docked/pinned controller resources to the current system context. - */ - public void preOnConfigurationChanged() { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.preOnConfigurationChanged(); - } - } - } - - /** - * @see DisplayContent#applyRotationLocked(int, int) - */ - public void applyRotation(int oldRotation, int newRotation) { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.applyRotationLocked(oldRotation, newRotation); - } - } - } - - public int getDisplayId() { - return mDisplayId; - } - - /** - * Called when the corresponding display receives - * {@link android.hardware.display.DisplayManager.DisplayListener#onDisplayChanged(int)}. - */ - public void onDisplayChanged() { - synchronized (mGlobalLock) { - if (mContainer == null) { - if (DEBUG_DISPLAY) Slog.i(TAG_WM, "onDisplayChanged: could not find display=" - + mDisplayId); - return; - } - mContainer.updateDisplayInfo(); - mService.mWindowPlacerLocked.requestTraversal(); - } - } - - /** - * Positions the task stack at the given position in the task stack container. - */ - public void positionChildAt(StackWindowController child, int position, - boolean includingParents) { - synchronized (mGlobalLock) { - if (DEBUG_STACK) Slog.i(TAG_WM, "positionTaskStackAt: positioning stack=" + child - + " at " + position); - if (mContainer == null) { - if (DEBUG_STACK) Slog.i(TAG_WM, - "positionTaskStackAt: could not find display=" + mContainer); - return; - } - if (child.mContainer == null) { - if (DEBUG_STACK) Slog.i(TAG_WM, - "positionTaskStackAt: could not find stack=" + this); - return; - } - mContainer.positionStackAt(position, child.mContainer, includingParents); - } - } - - /** - * Starts deferring the ability to update the IME target. This is needed when a call will - * attempt to update the IME target before all information about the Windows have been updated. - */ - public void deferUpdateImeTarget() { - synchronized (mGlobalLock) { - final DisplayContent dc = mRoot.getDisplayContent(mDisplayId); - if (dc != null) { - dc.deferUpdateImeTarget(); - } - } - } - - /** - * Resumes updating the IME target after deferring. See {@link #deferUpdateImeTarget()} - */ - public void continueUpdateImeTarget() { - synchronized (mGlobalLock) { - final DisplayContent dc = mRoot.getDisplayContent(mDisplayId); - if (dc != null) { - dc.continueUpdateImeTarget(); - } - } - } - - /** - * Sets a focused app on this display. - * - * @param token Specifies which app should be focused. - * @param moveFocusNow Specifies if we should update the focused window immediately. - */ - public void setFocusedApp(IBinder token, boolean moveFocusNow) { - synchronized (mGlobalLock) { - if (mContainer == null) { - if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "setFocusedApp: could not find displayId=" - + mDisplayId); - return; - } - final AppWindowToken newFocus; - if (token == null) { - if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Clearing focused app, displayId=" - + mDisplayId); - newFocus = null; - } else { - newFocus = mRoot.getAppWindowToken(token); - if (newFocus == null) { - Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token - + ", displayId=" + mDisplayId); - } - if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Set focused app to: " + newFocus - + " moveFocusNow=" + moveFocusNow + " displayId=" + mDisplayId); - } - - final boolean changed = mContainer.setFocusedApp(newFocus); - if (moveFocusNow && changed) { - mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, - true /*updateInputWindows*/); - } - } - } - - public void prepareAppTransition(@WindowManager.TransitionType int transit, - boolean alwaysKeepCurrent) { - prepareAppTransition(transit, alwaysKeepCurrent, 0 /* flags */, false /* forceOverride */); - } - - /** - * @param transit What kind of transition is happening. Use one of the constants - * AppTransition.TRANSIT_*. - * @param alwaysKeepCurrent If true and a transition is already set, new transition will NOT - * be set. - * @param flags Additional flags for the app transition, Use a combination of the constants - * AppTransition.TRANSIT_FLAG_*. - * @param forceOverride Always override the transit, not matter what was set previously. - */ - public void prepareAppTransition(@WindowManager.TransitionType int transit, - boolean alwaysKeepCurrent, @WindowManager.TransitionFlags int flags, - boolean forceOverride) { - synchronized (mGlobalLock) { - mRoot.getDisplayContent(mDisplayId).prepareAppTransition(transit, alwaysKeepCurrent, - flags, forceOverride); - } - } - - public void executeAppTransition() { - synchronized (mGlobalLock) { - mRoot.getDisplayContent(mDisplayId).executeAppTransition(); - } - } - - public void overridePendingAppTransition(String packageName, - int enterAnim, int exitAnim, IRemoteCallback startedCallback) { - synchronized (mGlobalLock) { - mRoot.getDisplayContent(mDisplayId).mAppTransition.overridePendingAppTransition( - packageName, enterAnim, exitAnim, startedCallback); - } - } - - public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth, - int startHeight) { - synchronized (mGlobalLock) { - mRoot.getDisplayContent(mDisplayId).mAppTransition.overridePendingAppTransitionScaleUp( - startX, startY, startWidth, startHeight); - } - } - - public void overridePendingAppTransitionClipReveal(int startX, int startY, - int startWidth, int startHeight) { - synchronized (mGlobalLock) { - mRoot.getDisplayContent(mDisplayId) - .mAppTransition.overridePendingAppTransitionClipReveal(startX, startY, - startWidth, startHeight); - } - } - - public void overridePendingAppTransitionThumb(GraphicBuffer srcThumb, int startX, - int startY, IRemoteCallback startedCallback, boolean scaleUp) { - synchronized (mGlobalLock) { - mRoot.getDisplayContent(mDisplayId) - .mAppTransition.overridePendingAppTransitionThumb(srcThumb, startX, startY, - startedCallback, scaleUp); - } - } - - public void overridePendingAppTransitionAspectScaledThumb(GraphicBuffer srcThumb, int startX, - int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback, - boolean scaleUp) { - synchronized (mGlobalLock) { - mRoot.getDisplayContent(mDisplayId) - .mAppTransition.overridePendingAppTransitionAspectScaledThumb(srcThumb, startX, - startY, targetWidth, targetHeight, startedCallback, scaleUp); - } - } - - public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs, - IRemoteCallback onAnimationStartedCallback, IRemoteCallback onAnimationFinishedCallback, - boolean scaleUp) { - synchronized (mGlobalLock) { - mRoot.getDisplayContent(mDisplayId) - .mAppTransition.overridePendingAppTransitionMultiThumb(specs, - onAnimationStartedCallback, onAnimationFinishedCallback, scaleUp); - } - } - - public void overridePendingAppTransitionStartCrossProfileApps() { - synchronized (mGlobalLock) { - mRoot.getDisplayContent(mDisplayId) - .mAppTransition.overridePendingAppTransitionStartCrossProfileApps(); - } - } - - public void overridePendingAppTransitionInPlace(String packageName, int anim) { - synchronized (mGlobalLock) { - mRoot.getDisplayContent(mDisplayId) - .mAppTransition.overrideInPlaceAppTransition(packageName, anim); - } - } - - /** - * Get Pending App transition of display. - * - * @return The pending app transition of the display. - */ - public @TransitionType int getPendingAppTransition() { - synchronized (mGlobalLock) { - return mRoot.getDisplayContent(mDisplayId).mAppTransition.getAppTransition(); - } - } - - /** - * Check if pending app transition is for activity / task launch. - */ - public boolean isNextTransitionForward() { - final int transit = getPendingAppTransition(); - return transit == TRANSIT_ACTIVITY_OPEN - || transit == TRANSIT_TASK_OPEN - || transit == TRANSIT_TASK_TO_FRONT; - } - - /** - * Checks if system decorations should be shown on this display. - * - * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS - */ - public boolean supportsSystemDecorations() { - synchronized (mGlobalLock) { - return mContainer.supportsSystemDecorations(); - } - } - - @Override - public String toString() { - return "{DisplayWindowController displayId=" + mDisplayId + "}"; - } -} diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 639ed02a1e48..f9c9d33c561a 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -1,5 +1,6 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -9,7 +10,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.os.Debug; import android.os.IBinder; import android.util.Slog; -import android.view.InputApplicationHandle; import android.view.KeyEvent; import android.view.WindowManager; @@ -204,6 +204,37 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal + WindowManagerService.TYPE_LAYER_OFFSET; } + /** Callback to get pointer display id. */ + @Override + public int getPointerDisplayId() { + synchronized (mService.mGlobalLock) { + // If desktop mode is not enabled, show on the default display. + if (!mService.mForceDesktopModeOnExternalDisplays) { + return DEFAULT_DISPLAY; + } + + // Look for the topmost freeform display. + int firstExternalDisplayId = DEFAULT_DISPLAY; + for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) { + final DisplayContent displayContent = mService.mRoot.mChildren.get(i); + // Heuristic solution here. Currently when "Freeform windows" developer option is + // enabled we automatically put secondary displays in freeform mode and emulating + // "desktop mode". It also makes sense to show the pointer on the same display. + if (displayContent.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + return displayContent.getDisplayId(); + } + + if (firstExternalDisplayId == DEFAULT_DISPLAY + && displayContent.getDisplayId() != DEFAULT_DISPLAY) { + firstExternalDisplayId = displayContent.getDisplayId(); + } + } + + // Look for the topmost non-default display + return firstExternalDisplayId; + } + } + /** Waits until the built-in input devices have been configured. */ public boolean waitForInputDevicesReady(long timeoutMillis) { synchronized (mInputDevicesReadyMonitor) { diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 4ef351390c16..5f56fe59c1f2 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -174,7 +174,7 @@ class KeyguardController { mWindowManager.deferSurfaceLayout(); try { setKeyguardGoingAway(true); - mRootActivityContainer.getDefaultDisplay().getWindowContainerController() + mRootActivityContainer.getDefaultDisplay().mDisplayContent .prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */, convertTransitFlags(flags), false /* forceOverride */); @@ -302,7 +302,7 @@ class KeyguardController { if (isKeyguardLocked()) { mWindowManager.deferSurfaceLayout(); try { - mRootActivityContainer.getDefaultDisplay().getWindowContainerController() + mRootActivityContainer.getDefaultDisplay().mDisplayContent .prepareAppTransition(resolveOccludeTransit(), false /* alwaysKeepCurrent */, 0 /* flags */, true /* forceOverride */); @@ -332,11 +332,11 @@ class KeyguardController { // If we are about to unocclude the Keyguard, but we can dismiss it without security, // we immediately dismiss the Keyguard so the activity gets shown without a flicker. - final DisplayWindowController dwc = - mRootActivityContainer.getDefaultDisplay().getWindowContainerController(); + final DisplayContent dc = + mRootActivityContainer.getDefaultDisplay().mDisplayContent; if (mKeyguardShowing && canDismissKeyguard() - && dwc.getPendingAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE) { - dwc.prepareAppTransition(mBeforeUnoccludeTransit, false /* alwaysKeepCurrent */, + && dc.mAppTransition.getAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE) { + dc.prepareAppTransition(mBeforeUnoccludeTransit, false /* alwaysKeepCurrent */, 0 /* flags */, true /* forceOverride */); mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); mWindowManager.executeAppTransition(); @@ -355,10 +355,10 @@ class KeyguardController { } private int resolveOccludeTransit() { - final DisplayWindowController dwc = - mRootActivityContainer.getDefaultDisplay().getWindowContainerController(); + final DisplayContent dc = + mService.mRootActivityContainer.getDefaultDisplay().mDisplayContent; if (mBeforeUnoccludeTransit != TRANSIT_UNSET - && dwc.getPendingAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE + && dc.mAppTransition.getAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE // TODO(b/113840485): Handle app transition for individual display. && isDisplayOccluded(DEFAULT_DISPLAY)) { @@ -369,7 +369,7 @@ class KeyguardController { } else if (!isDisplayOccluded(DEFAULT_DISPLAY)) { // Save transit in case we dismiss/occlude Keyguard shortly after. - mBeforeUnoccludeTransit = dwc.getPendingAppTransition(); + mBeforeUnoccludeTransit = dc.mAppTransition.getAppTransition(); return TRANSIT_KEYGUARD_UNOCCLUDE; } else { return TRANSIT_KEYGUARD_OCCLUDE; diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index d2f2863b8fd1..3b66f7d79752 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -582,7 +582,7 @@ public class LockTaskController { mSupervisor.mRootActivityContainer.resumeFocusedStacksTopActivities(); final ActivityStack stack = task.getStack(); if (stack != null) { - stack.getDisplay().getWindowContainerController().executeAppTransition(); + stack.getDisplay().mDisplayContent.executeAppTransition(); } } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) { mSupervisor.handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index 1e287b4e412e..42cd8e864322 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -47,7 +47,7 @@ import android.view.IRecentsAnimationRunner; import com.android.server.LocalServices; import com.android.server.am.AssistDataRequester; -import com.android.server.intelligence.IntelligenceManagerInternal; +import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks; import java.util.List; @@ -93,12 +93,12 @@ class RecentsAnimation implements RecentsAnimationCallbacks, Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "RecentsAnimation#startRecentsActivity"); // TODO(multi-display) currently only support recents animation in default display. - final DisplayWindowController dwc = - mService.mRootActivityContainer.getDefaultDisplay().getWindowContainerController(); + final DisplayContent dc = + mService.mRootActivityContainer.getDefaultDisplay().mDisplayContent; if (!mWindowManager.canStartRecentsAnimation()) { notifyAnimationCancelBeforeStart(recentsAnimationRunner); if (DEBUG) Slog.d(TAG, "Can't start recents animation, nextAppTransition=" - + dwc.getPendingAppTransition()); + + dc.mAppTransition.getAppTransition()); return; } @@ -107,7 +107,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, && recentsComponent.equals(intent.getComponent()) ? ACTIVITY_TYPE_RECENTS : ACTIVITY_TYPE_HOME; - final ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, + ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType); ActivityRecord targetActivity = getTargetActivity(targetStack, intent.getComponent()); final boolean hasExistingActivity = targetActivity != null; @@ -153,7 +153,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, "startRecentsActivity"); } } else { - // No recents activity + // No recents activity, create the new recents activity bottom most ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchActivityType(mTargetActivityType); options.setAvoidMoveToFront(); @@ -166,11 +166,20 @@ class RecentsAnimation implements RecentsAnimationCallbacks, .setActivityOptions(SafeActivityOptions.fromBundle(options.toBundle())) .setMayWait(userId) .execute(); - mWindowManager.prepareAppTransition(TRANSIT_NONE, false); - mWindowManager.executeAppTransition(); + // Move the recents activity into place for the animation targetActivity = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType).getTopActivity(); + targetStack = targetActivity.getActivityStack(); + mDefaultDisplay.moveStackBehindBottomMostVisibleStack(targetStack); + if (DEBUG) { + Slog.d(TAG, "Moved stack=" + targetStack + " behind stack=" + + mDefaultDisplay.getStackAbove(targetStack)); + } + + mWindowManager.prepareAppTransition(TRANSIT_NONE, false); + mWindowManager.executeAppTransition(); + // TODO: Maybe wait for app to draw in this particular case? @@ -225,8 +234,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) { // Try to notify the intelligence service first - final IntelligenceManagerInternal imService = - LocalServices.getService(IntelligenceManagerInternal.class); + final ContentCaptureManagerInternal imService = + LocalServices.getService(ContentCaptureManagerInternal.class); final IBinder activityToken = topActivities.get(activityIndex); if (imService == null || !imService.sendActivityAssistData(userId, activityToken, data)) { @@ -236,8 +245,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, } }; } else { - final IntelligenceManagerInternal imService = - LocalServices.getService(IntelligenceManagerInternal.class); + final ContentCaptureManagerInternal imService = + LocalServices.getService(ContentCaptureManagerInternal.class); if (imService == null) { // There is no intelligence service, so there is no point requesting assist data return; diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index 1d86b4684b32..c5b42f99fcda 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -864,7 +864,7 @@ class RootActivityContainer extends ConfigurationContainer // Resize the pinned stack to match the current size of the task the activity we are // going to be moving is currently contained in. We do this to have the right starting // animation bounds for the pinned stack to the desired bounds the caller wants. - resizeStack(stack, task.getOverrideBounds(), null /* tempTaskBounds */, + resizeStack(stack, task.getRequestedOverrideBounds(), null /* tempTaskBounds */, null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, true /* allowResizeInDockedMode */, !DEFER_RESUME); @@ -914,7 +914,7 @@ class RootActivityContainer extends ConfigurationContainer void executeAppTransitionForAllDisplay() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - display.getWindowContainerController().executeAppTransition(); + display.mDisplayContent.executeAppTransition(); } } @@ -1254,9 +1254,8 @@ class RootActivityContainer extends ConfigurationContainer } // TODO: remove after object merge with RootWindowContainer - void onChildPositionChanged(DisplayWindowController childController, int position) { + void onChildPositionChanged(ActivityDisplay display, int position) { // Assume AM lock is held from positionChildAt of controller in each hierarchy. - final ActivityDisplay display = getActivityDisplay(childController.getDisplayId()); if (display != null) { positionChildAt(display, position); } @@ -1281,8 +1280,7 @@ class RootActivityContainer extends ConfigurationContainer @VisibleForTesting void addChild(ActivityDisplay activityDisplay, int position) { positionChildAt(activityDisplay, position); - mRootWindowContainer.positionChildAt(position, - activityDisplay.getWindowContainerController().mContainer); + mRootWindowContainer.positionChildAt(position, activityDisplay.mDisplayContent); } void removeChild(ActivityDisplay activityDisplay) { @@ -1297,7 +1295,7 @@ class RootActivityContainer extends ConfigurationContainer throw new IllegalArgumentException("No display found with id: " + displayId); } - return activityDisplay.getOverrideConfiguration(); + return activityDisplay.getRequestedOverrideConfiguration(); } void setDisplayOverrideConfiguration(Configuration overrideConfiguration, int displayId) { @@ -1306,7 +1304,7 @@ class RootActivityContainer extends ConfigurationContainer throw new IllegalArgumentException("No display found with id: " + displayId); } - activityDisplay.onOverrideConfigurationChanged(overrideConfiguration); + activityDisplay.onRequestedOverrideConfigurationChanged(overrideConfiguration); } void prepareForShutdown() { @@ -2161,7 +2159,7 @@ class RootActivityContainer extends ConfigurationContainer for (int i = 0; i < displayCount; i++) { final ActivityDisplay activityDisplay = mActivityDisplays.get(i); pw.print(prefix); pw.print(" "); pw.print(activityDisplay.mDisplayId); pw.print(": "); - pw.println(activityDisplay.getOverrideConfiguration()); + pw.println(activityDisplay.getRequestedOverrideConfiguration()); } } @@ -2190,7 +2188,7 @@ class RootActivityContainer extends ConfigurationContainer + ": type=" + activityTypeToString(stack.getActivityType()) + " mode=" + windowingModeToString(stack.getWindowingMode())); pw.println(" isSleeping=" + stack.shouldSleepActivities()); - pw.println(" mBounds=" + stack.getOverrideBounds()); + pw.println(" mBounds=" + stack.getRequestedOverrideBounds()); printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage, needSep); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index b98d15479cf5..3bbef9248f23 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -224,7 +224,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return null; } - DisplayContent createDisplayContent(final Display display, DisplayWindowController controller) { + DisplayContent createDisplayContent(final Display display, ActivityDisplay activityDisplay) { final int displayId = display.getDisplayId(); // In select scenarios, it is possible that a DisplayContent will be created on demand @@ -233,17 +233,17 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final DisplayContent existing = getDisplayContent(displayId); if (existing != null) { - initializeDisplayOverrideConfiguration(controller, existing); - existing.setController(controller); + existing.mAcitvityDisplay = activityDisplay; + existing.initializeDisplayOverrideConfiguration(); return existing; } - final DisplayContent dc = new DisplayContent(display, mWmService, controller); + final DisplayContent dc = new DisplayContent(display, mWmService, activityDisplay); if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display); mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(dc); - initializeDisplayOverrideConfiguration(controller, dc); + dc.initializeDisplayOverrideConfiguration(); if (mWmService.mDisplayManagerInternal != null) { mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager( @@ -256,19 +256,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return dc; } - /** - * The display content may have configuration set from {@link #DisplayWindowSettings}. This - * callback let the owner of container know there is existing configuration to prevent the - * values from being replaced by the initializing {@link #ActivityDisplay}. - */ - private void initializeDisplayOverrideConfiguration(DisplayWindowController controller, - DisplayContent displayContent) { - if (controller != null && controller.mListener != null) { - controller.mListener.onInitializeOverrideConfiguration( - displayContent.getOverrideConfiguration()); - } - } - boolean isLayoutNeeded() { final int numDisplays = mChildren.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { @@ -356,13 +343,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void setDisplayOverrideConfigurationIfNeeded(Configuration newConfiguration, @NonNull DisplayContent displayContent) { - final Configuration currentConfig = displayContent.getOverrideConfiguration(); + final Configuration currentConfig = displayContent.getRequestedOverrideConfiguration(); final boolean configChanged = currentConfig.diff(newConfiguration) != 0; if (!configChanged) { return; } - displayContent.onOverrideConfigurationChanged(newConfiguration); + displayContent.onRequestedOverrideConfigurationChanged(newConfiguration); if (displayContent.getDisplayId() == DEFAULT_DISPLAY) { // Override configuration of the default display duplicates global config. In this case @@ -1040,7 +1027,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void positionChildAt(int position, DisplayContent child, boolean includingParents) { super.positionChildAt(position, child, includingParents); if (mRootActivityContainer != null) { - mRootActivityContainer.onChildPositionChanged(child.getController(), position); + mRootActivityContainer.onChildPositionChanged(child.mAcitvityDisplay, position); } } diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java index 8f18aa56b001..0452977dc422 100644 --- a/services/core/java/com/android/server/wm/StackWindowController.java +++ b/services/core/java/com/android/server/wm/StackWindowController.java @@ -67,107 +67,87 @@ public class StackWindowController mStackId = stackId; mHandler = new H(new WeakReference<>(this), service.mH.getLooper()); - synchronized (mGlobalLock) { - final DisplayContent dc = mRoot.getDisplayContent(displayId); - if (dc == null) { - throw new IllegalArgumentException("Trying to add stackId=" + stackId - + " to unknown displayId=" + displayId); - } - - dc.createStack(stackId, onTop, this); - getRawBounds(outBounds); + final DisplayContent dc = mRoot.getDisplayContent(displayId); + if (dc == null) { + throw new IllegalArgumentException("Trying to add stackId=" + stackId + + " to unknown displayId=" + displayId); } + + dc.createStack(stackId, onTop, this); + getRawBounds(outBounds); } @Override public void removeContainer() { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.removeIfPossible(); - super.removeContainer(); - } + if (mContainer != null) { + mContainer.removeIfPossible(); + super.removeContainer(); } } - public void reparent(int displayId, Rect outStackBounds, boolean onTop) { - synchronized (mGlobalLock) { - if (mContainer == null) { - throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId - + " to displayId=" + displayId); - } - - final DisplayContent targetDc = mRoot.getDisplayContent(displayId); - if (targetDc == null) { - throw new IllegalArgumentException("Trying to move stackId=" + mStackId - + " to unknown displayId=" + displayId); - } + void reparent(int displayId, Rect outStackBounds, boolean onTop) { + if (mContainer == null) { + throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId + + " to displayId=" + displayId); + } - targetDc.moveStackToDisplay(mContainer, onTop); - getRawBounds(outStackBounds); + final DisplayContent targetDc = mRoot.getDisplayContent(displayId); + if (targetDc == null) { + throw new IllegalArgumentException("Trying to move stackId=" + mStackId + + " to unknown displayId=" + displayId); } + + targetDc.moveStackToDisplay(mContainer, onTop); + getRawBounds(outStackBounds); } - public void positionChildAt(TaskWindowContainerController child, int position) { - synchronized (mGlobalLock) { - if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child - + " at " + position); - if (child.mContainer == null) { - if (DEBUG_STACK) Slog.i(TAG_WM, - "positionChildAt: could not find task=" + this); - return; + void positionChildAt(Task child, int position) { + if (DEBUG_STACK) { + Slog.i(TAG_WM, "positionChildAt: positioning task=" + child + " at " + position); + } + if (child == null) { + if (DEBUG_STACK) { + Slog.i(TAG_WM, "positionChildAt: could not find task=" + this); } - if (mContainer == null) { - if (DEBUG_STACK) Slog.i(TAG_WM, - "positionChildAt: could not find stack for task=" + mContainer); - return; + return; + } + if (mContainer == null) { + if (DEBUG_STACK) { + Slog.i(TAG_WM, "positionChildAt: could not find stack for task=" + mContainer); } - child.mContainer.positionAt(position); - mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); + return; } + child.positionAt(position); + mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); } - public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) { + void positionChildAtTop(Task child, boolean includingParents) { if (child == null) { // TODO: Fix the call-points that cause this to happen. return; } - synchronized (mGlobalLock) { - final Task childTask = child.mContainer; - if (childTask == null) { - Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found"); - return; - } - mContainer.positionChildAt(POSITION_TOP, childTask, includingParents); + mContainer.positionChildAt(POSITION_TOP, child, includingParents); - final DisplayContent displayContent = mContainer.getDisplayContent(); - if (displayContent.mAppTransition.isTransitionSet()) { - childTask.setSendingToBottom(false); - } - displayContent.layoutAndAssignWindowLayersIfNeeded(); + final DisplayContent displayContent = mContainer.getDisplayContent(); + if (displayContent.mAppTransition.isTransitionSet()) { + child.setSendingToBottom(false); } + displayContent.layoutAndAssignWindowLayersIfNeeded(); } - public void positionChildAtBottom(TaskWindowContainerController child, - boolean includingParents) { + void positionChildAtBottom(Task child, boolean includingParents) { if (child == null) { // TODO: Fix the call-points that cause this to happen. return; } - synchronized (mGlobalLock) { - final Task childTask = child.mContainer; - if (childTask == null) { - Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found"); - return; - } - mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents); + mContainer.positionChildAt(POSITION_BOTTOM, child, includingParents); - if (mContainer.getDisplayContent().mAppTransition.isTransitionSet()) { - childTask.setSendingToBottom(true); - } - mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); + if (mContainer.getDisplayContent().mAppTransition.isTransitionSet()) { + child.setSendingToBottom(true); } + mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); } /** @@ -179,24 +159,20 @@ public class StackWindowController */ public void resize(Rect bounds, SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) { - synchronized (mGlobalLock) { - if (mContainer == null) { - throw new IllegalArgumentException("resizeStack: stack " + this + " not found."); - } - // We might trigger a configuration change. Save the current task bounds for freezing. - mContainer.prepareFreezingTaskBounds(); - if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds) - && mContainer.isVisible()) { - mContainer.getDisplayContent().setLayoutNeeded(); - mService.mWindowPlacerLocked.performSurfacePlacement(); - } + if (mContainer == null) { + throw new IllegalArgumentException("resizeStack: stack " + this + " not found."); + } + // We might trigger a configuration change. Save the current task bounds for freezing. + mContainer.prepareFreezingTaskBounds(); + if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds) + && mContainer.isVisible()) { + mContainer.getDisplayContent().setLayoutNeeded(); + mService.mWindowPlacerLocked.performSurfacePlacement(); } } public void onPipAnimationEndResize() { - synchronized (mService.mGlobalLock) { - mContainer.onPipAnimationEndResize(); - } + mContainer.onPipAnimationEndResize(); } /** @@ -205,45 +181,37 @@ public class StackWindowController public void getStackDockedModeBounds(Configuration parentConfig, Rect dockedBounds, Rect currentTempTaskBounds, Rect outStackBounds, Rect outTempTaskBounds) { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.getStackDockedModeBoundsLocked(parentConfig, dockedBounds, - currentTempTaskBounds, outStackBounds, outTempTaskBounds); - return; - } - outStackBounds.setEmpty(); - outTempTaskBounds.setEmpty(); + if (mContainer != null) { + mContainer.getStackDockedModeBoundsLocked(parentConfig, dockedBounds, + currentTempTaskBounds, outStackBounds, outTempTaskBounds); + return; } + outStackBounds.setEmpty(); + outTempTaskBounds.setEmpty(); } public void prepareFreezingTaskBounds() { - synchronized (mGlobalLock) { - if (mContainer == null) { - throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this - + " not found."); - } - mContainer.prepareFreezingTaskBounds(); + if (mContainer == null) { + throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this + + " not found."); } + mContainer.prepareFreezingTaskBounds(); } public void getRawBounds(Rect outBounds) { - synchronized (mGlobalLock) { - if (mContainer.matchParentBounds()) { - outBounds.setEmpty(); - } else { - mContainer.getRawBounds(outBounds); - } + if (mContainer.matchParentBounds()) { + outBounds.setEmpty(); + } else { + mContainer.getRawBounds(outBounds); } } public void getBounds(Rect outBounds) { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.getBounds(outBounds); - return; - } - outBounds.setEmpty(); + if (mContainer != null) { + mContainer.getBounds(outBounds); + return; } + outBounds.setEmpty(); } /** @@ -256,73 +224,71 @@ public class StackWindowController Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth, boolean overrideHeight, float density, Configuration config, Configuration parentConfig, int windowingMode) { - synchronized (mGlobalLock) { - final TaskStack stack = mContainer; - final DisplayContent displayContent = stack.getDisplayContent(); - final DisplayInfo di = displayContent.getDisplayInfo(); - final DisplayCutout displayCutout = di.displayCutout; - final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); - - // Get the insets and display bounds - displayPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, - displayCutout, mTmpStableInsets); - displayPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, - displayCutout, mTmpNonDecorInsets); - mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight); - - int width; - int height; - - final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds(); - - config.windowConfiguration.setBounds(bounds); - config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null); - boolean intersectParentBounds = false; - - if (WindowConfiguration.isFloating(windowingMode)) { - // Floating tasks should not be resized to the screen's bounds. - - if (windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED - && bounds.width() == mTmpDisplayBounds.width() - && bounds.height() == mTmpDisplayBounds.height()) { - // If the bounds we are animating is the same as the fullscreen stack - // dimensions, then apply the same inset calculations that we normally do for - // the fullscreen stack, without intersecting it with the display bounds - stableBounds.inset(mTmpStableInsets); - nonDecorBounds.inset(mTmpNonDecorInsets); - // Move app bounds to zero to apply intersection with parent correctly. They are - // used only for evaluating width and height, so it's OK to move them around. - config.windowConfiguration.getAppBounds().offsetTo(0, 0); - intersectParentBounds = true; - } - width = (int) (stableBounds.width() / density); - height = (int) (stableBounds.height() / density); - } else { - // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen - // area, i.e. the screen area without the system bars. - // Additionally task dimensions should not be bigger than its parents dimensions. - // The non decor inset are areas that could never be removed in Honeycomb. See - // {@link WindowManagerPolicy#getNonDecorInsetsLw}. - intersectDisplayBoundsExcludeInsets(nonDecorBounds, bounds, mTmpNonDecorInsets, - mTmpDisplayBounds, overrideWidth, overrideHeight); - intersectDisplayBoundsExcludeInsets(stableBounds, bounds, mTmpStableInsets, - mTmpDisplayBounds, overrideWidth, overrideHeight); - width = Math.min((int) (stableBounds.width() / density), - parentConfig.screenWidthDp); - height = Math.min((int) (stableBounds.height() / density), - parentConfig.screenHeightDp); + final TaskStack stack = mContainer; + final DisplayContent displayContent = stack.getDisplayContent(); + final DisplayInfo di = displayContent.getDisplayInfo(); + final DisplayCutout displayCutout = di.displayCutout; + final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); + + // Get the insets and display bounds + displayPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, + displayCutout, mTmpStableInsets); + displayPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, + displayCutout, mTmpNonDecorInsets); + mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight); + + int width; + int height; + + final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds(); + + config.windowConfiguration.setBounds(bounds); + config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null); + boolean intersectParentBounds = false; + + if (WindowConfiguration.isFloating(windowingMode)) { + // Floating tasks should not be resized to the screen's bounds. + + if (windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED + && bounds.width() == mTmpDisplayBounds.width() + && bounds.height() == mTmpDisplayBounds.height()) { + // If the bounds we are animating is the same as the fullscreen stack + // dimensions, then apply the same inset calculations that we normally do for + // the fullscreen stack, without intersecting it with the display bounds + stableBounds.inset(mTmpStableInsets); + nonDecorBounds.inset(mTmpNonDecorInsets); + // Move app bounds to zero to apply intersection with parent correctly. They are + // used only for evaluating width and height, so it's OK to move them around. + config.windowConfiguration.getAppBounds().offsetTo(0, 0); intersectParentBounds = true; } + width = (int) (stableBounds.width() / density); + height = (int) (stableBounds.height() / density); + } else { + // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen + // area, i.e. the screen area without the system bars. + // Additionally task dimensions should not be bigger than its parents dimensions. + // The non decor inset are areas that could never be removed in Honeycomb. See + // {@link WindowManagerPolicy#getNonDecorInsetsLw}. + intersectDisplayBoundsExcludeInsets(nonDecorBounds, bounds, mTmpNonDecorInsets, + mTmpDisplayBounds, overrideWidth, overrideHeight); + intersectDisplayBoundsExcludeInsets(stableBounds, bounds, mTmpStableInsets, + mTmpDisplayBounds, overrideWidth, overrideHeight); + width = Math.min((int) (stableBounds.width() / density), + parentConfig.screenWidthDp); + height = Math.min((int) (stableBounds.height() / density), + parentConfig.screenHeightDp); + intersectParentBounds = true; + } - if (intersectParentBounds && config.windowConfiguration.getAppBounds() != null) { - config.windowConfiguration.getAppBounds().intersect(parentAppBounds); - } - - config.screenWidthDp = width; - config.screenHeightDp = height; - config.smallestScreenWidthDp = getSmallestWidthForTaskBounds( - bounds, density, windowingMode); + if (intersectParentBounds && config.windowConfiguration.getAppBounds() != null) { + config.windowConfiguration.getAppBounds().intersect(parentAppBounds); } + + config.screenWidthDp = width; + config.screenHeightDp = height; + config.smallestScreenWidthDp = getSmallestWidthForTaskBounds( + bounds, density, windowingMode); } /** diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index c70f0751ef96..69d9810dd207 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -24,6 +24,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.res.Configuration.EMPTY; import static com.android.server.EventLogTags.WM_TASK_REMOVED; +import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.TaskProto.APP_WINDOW_TOKENS; import static com.android.server.wm.TaskProto.BOUNDS; import static com.android.server.wm.TaskProto.DEFER_REMOVAL; @@ -38,6 +39,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.CallSuper; +import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -54,7 +56,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.util.function.Consumer; -class Task extends WindowContainer<AppWindowToken> { +class Task extends WindowContainer<AppWindowToken> implements ConfigurationContainerListener{ static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM; // TODO: Track parent marks like this in WindowContainer. @@ -109,17 +111,25 @@ class Task extends WindowContainer<AppWindowToken> { /** @see #setCanAffectSystemUiFlags */ private boolean mCanAffectSystemUiFlags = true; + // TODO: remove after unification + TaskRecord mTaskRecord; + Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode, boolean supportsPictureInPicture, TaskDescription taskDescription, - TaskWindowContainerController controller) { + TaskRecord taskRecord) { super(service); mTaskId = taskId; mStack = stack; mUserId = userId; mResizeMode = resizeMode; mSupportsPictureInPicture = supportsPictureInPicture; - setController(controller); - setBounds(getOverrideBounds()); + mTaskRecord = taskRecord; + if (mTaskRecord != null) { + // This can be null when we call createTaskInStack in WindowTestUtils. Remove this after + // unification. + mTaskRecord.registerConfigurationChangeListener(this); + } + setBounds(getRequestedOverrideBounds()); mTaskDescription = taskDescription; // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED). @@ -195,6 +205,21 @@ class Task extends WindowContainer<AppWindowToken> { super.removeImmediately(); } + void reparent(StackWindowController stackController, int position, boolean moveParents) { + if (DEBUG_STACK) { + Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId + + " to stack=" + stackController + " at " + position); + } + final TaskStack stack = stackController.mContainer; + if (stack == null) { + throw new IllegalArgumentException("reparent: could not find stack=" + + stackController); + } + reparent(stack, position, moveParents); + getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); + } + + void reparent(TaskStack stack, int position, boolean moveParents) { if (stack == mStack) { throw new IllegalArgumentException( @@ -300,6 +325,12 @@ class Task extends WindowContainer<AppWindowToken> { return boundsChange; } + void resize(boolean relayout, boolean forced) { + if (setBounds(getRequestedOverrideBounds(), forced) != BOUNDS_CHANGE_NONE && relayout) { + getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); + } + } + @Override void onDisplayChanged(DisplayContent dc) { adjustBoundsForDisplayChangeIfNeeded(dc); @@ -369,7 +400,7 @@ class Task extends WindowContainer<AppWindowToken> { * the adjusted bounds's top. */ void alignToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) { - if (!isResizeable() || EMPTY.equals(getOverrideConfiguration())) { + if (!isResizeable() || EMPTY.equals(getRequestedOverrideConfiguration())) { return; } @@ -515,6 +546,15 @@ class Task extends WindowContainer<AppWindowToken> { return mDragResizeMode; } + /** + * Puts this task into docked drag resizing mode. See {@link DragResizeMode}. + * + * @param resizing Whether to put the task into drag resize mode. + */ + public void setTaskDockedResizing(boolean resizing) { + setDragResizing(resizing, DRAG_RESIZE_MODE_DOCKED_DIVIDER); + } + private void adjustBoundsForDisplayChangeIfNeeded(final DisplayContent displayContent) { if (displayContent == null) { return; @@ -556,9 +596,8 @@ class Task extends WindowContainer<AppWindowToken> { displayContent.rotateBounds(mRotation, newRotation, mTmpRect2); if (setBounds(mTmpRect2) != BOUNDS_CHANGE_NONE) { - final TaskWindowContainerController controller = getController(); - if (controller != null) { - controller.requestResize(getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION); + if (mTaskRecord != null) { + mTaskRecord.requestResize(getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION); } } } @@ -631,6 +670,20 @@ class Task extends WindowContainer<AppWindowToken> { return null; } + void positionChildAtTop(AppWindowToken aToken) { + positionChildAt(aToken, POSITION_TOP); + } + + void positionChildAt(AppWindowToken aToken, int position) { + if (aToken == null) { + Slog.w(TAG_WM, + "Attempted to position of non-existing app"); + return; + } + + positionChildAt(position, aToken, false /* includeParents */); + } + boolean isFullscreen() { if (useCurrentBounds()) { return matchParentBounds(); @@ -656,6 +709,10 @@ class Task extends WindowContainer<AppWindowToken> { mTaskDescription = taskDescription; } + void onSnapshotChanged(ActivityManager.TaskSnapshot snapshot) { + mTaskRecord.onSnapshotChanged(snapshot); + } + TaskDescription getTaskDescription() { return mTaskDescription; } @@ -666,11 +723,6 @@ class Task extends WindowContainer<AppWindowToken> { } @Override - TaskWindowContainerController getController() { - return (TaskWindowContainerController) super.getController(); - } - - @Override void forAllTasks(Consumer<Task> callback) { callback.accept(this); } diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 3cf0bd793c64..56e634a51fe1 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -47,6 +47,7 @@ import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.server.EventLogTags.WM_TASK_CREATED; import static com.android.server.am.TaskRecordProto.ACTIVITIES; import static com.android.server.am.TaskRecordProto.ACTIVITY_TYPE; import static com.android.server.am.TaskRecordProto.BOUNDS; @@ -76,6 +77,10 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECEN import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; +import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static java.lang.Integer.MAX_VALUE; @@ -106,6 +111,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.service.voice.IVoiceInteractionSession; import android.util.DisplayMetrics; +import android.util.EventLog; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -125,8 +131,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; -// TODO: Make package private again once move to WM package is complete. -public class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener { +class TaskRecord extends ConfigurationContainer { private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_ATM; private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; @@ -318,7 +323,8 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont /** Helper object used for updating override configuration. */ private Configuration mTmpConfig = new Configuration(); - private TaskWindowContainerController mWindowContainerController; + // TODO: remove after unification + Task mTask; /** * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int, @@ -424,43 +430,54 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont mService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity); } - TaskWindowContainerController getWindowContainerController() { - return mWindowContainerController; + Task getTask() { + return mTask; } - void createWindowContainer(boolean onTop, boolean showForAllUsers) { - if (mWindowContainerController != null) { - throw new IllegalArgumentException("Window container=" + mWindowContainerController + void createTask(boolean onTop, boolean showForAllUsers) { + if (mTask != null) { + throw new IllegalArgumentException("mTask=" + mTask + " already created for task=" + this); } final Rect bounds = updateOverrideConfigurationFromLaunchBounds(); - setWindowContainerController(new TaskWindowContainerController(taskId, this, - getStack().getWindowContainerController(), userId, bounds, - mResizeMode, mSupportsPictureInPicture, onTop, - showForAllUsers, lastTaskDescription)); - } + final StackWindowController stackController = getStack().getWindowContainerController(); - /** - * Should only be invoked from {@link #createWindowContainer(boolean, boolean)}. - */ - @VisibleForTesting - protected void setWindowContainerController(TaskWindowContainerController controller) { - if (mWindowContainerController != null) { - throw new IllegalArgumentException("Window container=" + mWindowContainerController - + " already created for task=" + this); + if (DEBUG_STACK) { + Slog.i(TAG_WM, "TaskRecord: taskId=" + taskId + + " stack=" + stackController + " bounds=" + bounds); } - mWindowContainerController = controller; - if (!mDisplayedBounds.isEmpty() && controller.mContainer != null) { - controller.mContainer.setOverrideDisplayedBounds(mDisplayedBounds); + final TaskStack stack = stackController.mContainer; + if (stack == null) { + throw new IllegalArgumentException("TaskRecord: invalid stack=" + + stackController); } + EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId); + mTask = new Task(taskId, stack, userId, mService.mWindowManager, mResizeMode, + mSupportsPictureInPicture, lastTaskDescription, this); + final int position = onTop ? POSITION_TOP : POSITION_BOTTOM; + + if (!mDisplayedBounds.isEmpty()) { + mTask.setOverrideDisplayedBounds(mDisplayedBounds); + } + // We only want to move the parents to the parents if we are creating this task at the + // top of its stack. + stack.addTask(mTask, position, showForAllUsers, onTop /* moveParents */); + } + + void setTask(Task task) { + mTask = task; } void removeWindowContainer() { mService.getLockTaskController().clearLockedTask(this); - mWindowContainerController.removeContainer(); - mWindowContainerController = null; + if (mTask == null) { + if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: could not find taskId=" + taskId); + return; + } + mTask.removeIfPossible(); + mTask = null; if (!getWindowConfiguration().persistTaskBounds()) { // Reset current bounds for task whose bounds shouldn't be persisted so it uses // default configuration the next time it launches. @@ -469,7 +486,6 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont mService.getTaskChangeNotificationController().notifyTaskRemoved(taskId); } - @Override public void onSnapshotChanged(TaskSnapshot snapshot) { mService.getTaskChangeNotificationController().notifyTaskSnapshotChanged(taskId, snapshot); } @@ -479,17 +495,20 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont return; } mResizeMode = resizeMode; - mWindowContainerController.setResizeable(resizeMode); + mTask.setResizeable(resizeMode); mService.mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); mService.mRootActivityContainer.resumeFocusedStacksTopActivities(); } void setTaskDockedResizing(boolean resizing) { - mWindowContainerController.setTaskDockedResizing(resizing); + if (mTask == null) { + Slog.w(TAG_WM, "setTaskDockedResizing: taskId " + taskId + " not found."); + return; + } + mTask.setTaskDockedResizing(resizing); } // TODO: Consolidate this with the resize() method below. - @Override public void requestResize(Rect bounds, int resizeMode) { mService.resizeTask(taskId, bounds, resizeMode); } @@ -506,12 +525,12 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont // If this is a forced resize, let it go through even if the bounds is not changing, // as we might need a relayout due to surface size change (to/from fullscreen). final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0; - if (equivalentOverrideBounds(bounds) && !forced) { + if (equivalentRequestedOverrideBounds(bounds) && !forced) { // Nothing to do here... return true; } - if (mWindowContainerController == null) { + if (mTask == null) { // Task doesn't exist in window manager yet (e.g. was restored from recents). // All we can do for now is update the bounds so it can be used when the task is // added to window manager. @@ -558,7 +577,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont } } } - mWindowContainerController.resize(kept, forced); + mTask.resize(kept, forced); saveLaunchingStateIfNeeded(); @@ -571,11 +590,15 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont // TODO: Investigate combining with the resize() method above. void resizeWindowContainer() { - mWindowContainerController.resize(false /* relayout */, false /* forced */); + mTask.resize(false /* relayout */, false /* forced */); } void getWindowContainerBounds(Rect bounds) { - mWindowContainerController.getBounds(bounds); + if (mTask != null) { + mTask.getBounds(bounds); + } else { + bounds.setEmpty(); + } } /** @@ -679,7 +702,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont // Must reparent first in window manager to avoid a situation where AM can delete the // we are coming from in WM before we reparent because it became empty. - mWindowContainerController.reparent(toStack.getWindowContainerController(), position, + mTask.reparent(toStack.getWindowContainerController(), position, moveStackMode == REPARENT_MOVE_STACK_TO_FRONT); final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT @@ -720,12 +743,12 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont // Make sure the task has the appropriate bounds/size for the stack it is in. final boolean toStackSplitScreenPrimary = toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; - final Rect configBounds = getOverrideBounds(); + final Rect configBounds = getRequestedOverrideBounds(); if ((toStackWindowingMode == WINDOWING_MODE_FULLSCREEN || toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) - && !Objects.equals(configBounds, toStack.getOverrideBounds())) { - kept = resize(toStack.getOverrideBounds(), RESIZE_MODE_SYSTEM, !mightReplaceWindow, - deferResume); + && !Objects.equals(configBounds, toStack.getRequestedOverrideBounds())) { + kept = resize(toStack.getRequestedOverrideBounds(), RESIZE_MODE_SYSTEM, + !mightReplaceWindow, deferResume); } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) { Rect bounds = getLaunchBounds(); if (bounds == null) { @@ -739,8 +762,8 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont // mode mService.mStackSupervisor.moveRecentsStackToFront(reason); } - kept = resize(toStack.getOverrideBounds(), RESIZE_MODE_SYSTEM, !mightReplaceWindow, - deferResume); + kept = resize(toStack.getRequestedOverrideBounds(), RESIZE_MODE_SYSTEM, + !mightReplaceWindow, deferResume); } } finally { windowManager.continueSurfaceLayout(); @@ -779,7 +802,11 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont } void cancelWindowTransition() { - mWindowContainerController.cancelWindowTransition(); + if (mTask == null) { + Slog.w(TAG_WM, "cancelWindowTransition: taskId " + taskId + " not found."); + return; + } + mTask.cancelTaskWindowTransition(); } /** @@ -1190,7 +1217,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont mActivities.add(newTop); // Make sure window manager is aware of the position change. - mWindowContainerController.positionChildAtTop(newTop.mAppWindowToken); + mTask.positionChildAtTop(newTop.mAppWindowToken); updateEffectiveIntent(); setFrontOfTask(); @@ -1275,7 +1302,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont if (r.mAppWindowToken != null) { // Only attempt to move in WM if the child has a controller. It is possible we haven't // created controller for the activity we are starting yet. - mWindowContainerController.positionChildAt(r.mAppWindowToken, index); + mTask.positionChildAt(r.mAppWindowToken, index); } // Make sure the list of display UID whitelists is updated @@ -1546,7 +1573,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont return true; } final boolean landscape = bounds.width() > bounds.height(); - final Rect configBounds = getOverrideBounds(); + final Rect configBounds = getRequestedOverrideBounds(); if (mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION) { return configBounds.isEmpty() || landscape == (configBounds.width() > configBounds.height()); @@ -1643,8 +1670,8 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont } lastTaskDescription = new TaskDescription(label, null, iconResource, iconFilename, colorPrimary, colorBackground, statusBarColor, navigationBarColor); - if (mWindowContainerController != null) { - mWindowContainerController.setTaskDescription(lastTaskDescription); + if (mTask != null) { + mTask.setTaskDescription(lastTaskDescription); } // Update the task affiliation color if we are the parent of the group if (taskId == mAffiliatedTaskId) { @@ -1709,7 +1736,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont return; } - final Rect configBounds = getOverrideBounds(); + final Rect configBounds = getRequestedOverrideBounds(); if (adjustWidth) { if (!configBounds.isEmpty() && bounds.right == configBounds.right) { bounds.left = bounds.right - minWidth; @@ -1739,7 +1766,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont // (bounds == null), then leave the override config unset final Configuration newOverrideConfig = new Configuration(); if (bounds != null) { - newOverrideConfig.setTo(getOverrideConfiguration()); + newOverrideConfig.setTo(getRequestedOverrideConfiguration()); if (insetBounds != null && !insetBounds.isEmpty()) { mTmpRect.set(insetBounds); setDisplayedBounds(bounds); @@ -1781,13 +1808,13 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont * @return True if the override configuration was updated. */ boolean updateOverrideConfiguration(Rect bounds, @Nullable Rect insetBounds) { - if (equivalentOverrideBounds(bounds)) { + if (equivalentRequestedOverrideBounds(bounds)) { return false; } - final Rect currentBounds = getOverrideBounds(); + final Rect currentBounds = getRequestedOverrideBounds(); - mTmpConfig.setTo(getOverrideConfiguration()); - final Configuration newConfig = getOverrideConfiguration(); + mTmpConfig.setTo(getRequestedOverrideConfiguration()); + final Configuration newConfig = getRequestedOverrideConfiguration(); final boolean matchParentBounds = bounds == null || bounds.isEmpty(); final boolean persistBounds = getWindowConfiguration().persistTaskBounds(); @@ -1810,12 +1837,12 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont setBounds(mTmpRect); if (mStack == null || persistBounds) { - setLastNonFullscreenBounds(getOverrideBounds()); + setLastNonFullscreenBounds(getRequestedOverrideBounds()); } computeOverrideConfiguration(newConfig, mTmpRect, mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom); } - onOverrideConfigurationChanged(newConfig); + onRequestedOverrideConfigurationChanged(newConfig); return !mTmpConfig.equals(newConfig); } @@ -1879,9 +1906,8 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont } else { mDisplayedBounds.set(bounds); } - final TaskWindowContainerController controller = getWindowContainerController(); - if (controller != null && controller.mContainer != null) { - controller.mContainer.setOverrideDisplayedBounds( + if (mTask != null) { + mTask.setOverrideDisplayedBounds( mDisplayedBounds.isEmpty() ? null : mDisplayedBounds); } } @@ -1949,7 +1975,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont if (bounds != null && !bounds.isEmpty()) { // TODO: Review if we actually want to do this - we are setting the launch bounds // directly here. - bounds.set(getOverrideBounds()); + bounds.set(getRequestedOverrideBounds()); } return bounds; } @@ -1975,7 +2001,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont mService.mStackSupervisor.getLaunchParamsController().layoutTask(this, null); } } else { - updateOverrideConfiguration(inStack.getOverrideBounds()); + updateOverrideConfiguration(inStack.getRequestedOverrideBounds()); } } @@ -1989,9 +2015,9 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont if (!isActivityTypeStandardOrUndefined() || windowingMode == WINDOWING_MODE_FULLSCREEN || (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !isResizeable())) { - return isResizeable() ? mStack.getOverrideBounds() : null; + return isResizeable() ? mStack.getRequestedOverrideBounds() : null; } else if (!getWindowConfiguration().persistTaskBounds()) { - return mStack.getOverrideBounds(); + return mStack.getRequestedOverrideBounds(); } return mLastNonFullscreenBounds; } @@ -2210,7 +2236,7 @@ public class TaskRecord extends ConfigurationContainer implements TaskWindowCont proto.write(FULLSCREEN, matchParentBounds()); if (!matchParentBounds()) { - final Rect bounds = getOverrideBounds(); + final Rect bounds = getRequestedOverrideBounds(); bounds.writeToProto(proto, BOUNDS); } proto.write(MIN_WIDTH, mMinWidth); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 7ab4d086b70a..01a5622c2b60 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -191,9 +191,7 @@ class TaskSnapshotController { } else { mCache.putSnapshot(task, snapshot); mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); - if (task.getController() != null) { - task.getController().reportSnapshotChanged(snapshot); - } + task.onSnapshotChanged(snapshot); } } } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index b16e184c81af..11d9ebb59db0 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -282,7 +282,7 @@ public class TaskStack extends WindowContainer<Task> implements @Override public int setBounds(Rect bounds) { - return setBounds(getOverrideBounds(), bounds); + return setBounds(getRequestedOverrideBounds(), bounds); } private int setBounds(Rect existing, Rect bounds) { @@ -304,8 +304,8 @@ public class TaskStack extends WindowContainer<Task> implements /** Bounds of the stack without adjusting for other factors in the system like visibility * of docked stack. - * Most callers should be using {@link ConfigurationContainer#getOverrideBounds} as it take into - * consideration other system factors. */ + * Most callers should be using {@link ConfigurationContainer#getRequestedOverrideBounds} a + * it takes into consideration other system factors. */ void getRawBounds(Rect out) { out.set(getRawBounds()); } @@ -448,7 +448,8 @@ public class TaskStack extends WindowContainer<Task> implements * @param inOutBounds the bounds to update (both input and output). */ void calculateDockedBoundsForConfigChange(Configuration parentConfig, Rect inOutBounds) { - final boolean primary = getOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; + final boolean primary = + getRequestedOverrideWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; repositionSplitScreenStackAfterRotation(parentConfig, primary, inOutBounds); final DisplayCutout cutout = mDisplayContent.getDisplayInfo().displayCutout; snapDockedStackAfterRotation(parentConfig, cutout, inOutBounds); diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java deleted file mode 100644 index ec64d2e82895..000000000000 --- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static com.android.server.EventLogTags.WM_TASK_CREATED; -import static com.android.server.wm.ConfigurationContainer.BOUNDS_CHANGE_NONE; -import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; -import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; -import static com.android.server.wm.WindowContainer.POSITION_TOP; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; - -import android.app.ActivityManager.TaskDescription; -import android.app.ActivityManager.TaskSnapshot; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.EventLog; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; - -import java.lang.ref.WeakReference; - -/** - * Controller for the task container. This is created by activity manager to link task records to - * the task container they use in window manager. - * - * Test class: {@link TaskWindowContainerControllerTests} - */ -public class TaskWindowContainerController - extends WindowContainerController<Task, TaskWindowContainerListener> { - - private final int mTaskId; - private final H mHandler; - - public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener, - StackWindowController stackController, int userId, Rect bounds, int resizeMode, - boolean supportsPictureInPicture, boolean toTop, boolean showForAllUsers, - TaskDescription taskDescription) { - this(taskId, listener, stackController, userId, bounds, resizeMode, - supportsPictureInPicture, toTop, showForAllUsers, taskDescription, - WindowManagerService.getInstance()); - } - - public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener, - StackWindowController stackController, int userId, Rect bounds, int resizeMode, - boolean supportsPictureInPicture, boolean toTop, boolean showForAllUsers, - TaskDescription taskDescription, WindowManagerService service) { - super(listener, service); - mTaskId = taskId; - mHandler = new H(new WeakReference<>(this), service.mH.getLooper()); - - synchronized (mGlobalLock) { - if (DEBUG_STACK) Slog.i(TAG_WM, "TaskWindowContainerController: taskId=" + taskId - + " stack=" + stackController + " bounds=" + bounds); - - final TaskStack stack = stackController.mContainer; - if (stack == null) { - throw new IllegalArgumentException("TaskWindowContainerController: invalid stack=" - + stackController); - } - EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId); - final Task task = createTask(taskId, stack, userId, resizeMode, - supportsPictureInPicture, taskDescription); - final int position = toTop ? POSITION_TOP : POSITION_BOTTOM; - // We only want to move the parents to the parents if we are creating this task at the - // top of its stack. - stack.addTask(task, position, showForAllUsers, toTop /* moveParents */); - } - } - - @VisibleForTesting - Task createTask(int taskId, TaskStack stack, int userId, int resizeMode, - boolean supportsPictureInPicture, TaskDescription taskDescription) { - return new Task(taskId, stack, userId, mService, resizeMode, supportsPictureInPicture, - taskDescription, this); - } - - @Override - public void removeContainer() { - synchronized (mGlobalLock) { - if (mContainer == null) { - if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: could not find taskId=" + mTaskId); - return; - } - mContainer.removeIfPossible(); - super.removeContainer(); - } - } - - void positionChildAtTop(AppWindowToken aToken) { - positionChildAt(aToken, POSITION_TOP); - } - - void positionChildAt(AppWindowToken aToken, int position) { - synchronized (mService.mGlobalLock) { - if (aToken == null) { - Slog.w(TAG_WM, - "Attempted to position of non-existing app"); - return; - } - - final Task task = mContainer; - if (task == null) { - throw new IllegalArgumentException("positionChildAt: invalid task=" + this); - } - task.positionChildAt(position, aToken, false /* includeParents */); - } - } - - public void reparent(StackWindowController stackController, int position, boolean moveParents) { - synchronized (mGlobalLock) { - if (DEBUG_STACK) Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId - + " to stack=" + stackController + " at " + position); - if (mContainer == null) { - if (DEBUG_STACK) Slog.i(TAG_WM, - "reparent: could not find taskId=" + mTaskId); - return; - } - final TaskStack stack = stackController.mContainer; - if (stack == null) { - throw new IllegalArgumentException("reparent: could not find stack=" - + stackController); - } - mContainer.reparent(stack, position, moveParents); - mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); - } - } - - public void setResizeable(int resizeMode) { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.setResizeable(resizeMode); - } - } - } - - public void resize(boolean relayout, boolean forced) { - synchronized (mGlobalLock) { - if (mContainer == null) { - throw new IllegalArgumentException("resizeTask: taskId " + mTaskId + " not found."); - } - - if (mContainer.setBounds(mContainer.getOverrideBounds(), forced) != BOUNDS_CHANGE_NONE - && relayout) { - mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); - } - } - } - - public void getBounds(Rect bounds) { - synchronized (mGlobalLock) { - if (mContainer != null) { - mContainer.getBounds(bounds); - return; - } - bounds.setEmpty(); - } - } - - /** - * Puts this task into docked drag resizing mode. See {@link DragResizeMode}. - * - * @param resizing Whether to put the task into drag resize mode. - */ - public void setTaskDockedResizing(boolean resizing) { - synchronized (mGlobalLock) { - if (mContainer == null) { - Slog.w(TAG_WM, "setTaskDockedResizing: taskId " + mTaskId + " not found."); - return; - } - mContainer.setDragResizing(resizing, DRAG_RESIZE_MODE_DOCKED_DIVIDER); - } - } - - public void cancelWindowTransition() { - synchronized (mGlobalLock) { - if (mContainer == null) { - Slog.w(TAG_WM, "cancelWindowTransition: taskId " + mTaskId + " not found."); - return; - } - mContainer.cancelTaskWindowTransition(); - } - } - - public void setTaskDescription(TaskDescription taskDescription) { - synchronized (mGlobalLock) { - if (mContainer == null) { - Slog.w(TAG_WM, "setTaskDescription: taskId " + mTaskId + " not found."); - return; - } - mContainer.setTaskDescription(taskDescription); - } - } - - public boolean isDragResizing() { - synchronized (mGlobalLock) { - return mContainer.isDragResizing(); - } - } - - void reportSnapshotChanged(TaskSnapshot snapshot) { - mHandler.obtainMessage(H.REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget(); - } - - void requestResize(Rect bounds, int resizeMode) { - mHandler.obtainMessage(H.REQUEST_RESIZE, resizeMode, 0, bounds).sendToTarget(); - } - - @Override - public String toString() { - return "{TaskWindowContainerController taskId=" + mTaskId + "}"; - } - - private static final class H extends Handler { - - static final int REPORT_SNAPSHOT_CHANGED = 0; - static final int REQUEST_RESIZE = 1; - - private final WeakReference<TaskWindowContainerController> mController; - - H(WeakReference<TaskWindowContainerController> controller, Looper looper) { - super(looper); - mController = controller; - } - - @Override - public void handleMessage(Message msg) { - final TaskWindowContainerController controller = mController.get(); - final TaskWindowContainerListener listener = (controller != null) - ? controller.mListener : null; - if (listener == null) { - return; - } - switch (msg.what) { - case REPORT_SNAPSHOT_CHANGED: - listener.onSnapshotChanged((TaskSnapshot) msg.obj); - break; - case REQUEST_RESIZE: - listener.requestResize((Rect) msg.obj, msg.arg1); - break; - } - } - } -} diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java deleted file mode 100644 index af67de38e5b3..000000000000 --- a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import android.app.ActivityManager.TaskSnapshot; -import android.graphics.Rect; - -/** - * Interface used by the creator of {@link TaskWindowContainerController} to listen to changes with - * the task container. - */ -public interface TaskWindowContainerListener extends WindowContainerListener { - - /** Called when the snapshot of this task has changed. */ - void onSnapshotChanged(TaskSnapshot snapshot); - - /** Called when the task container would like its controller to resize. */ - void requestResize(Rect bounds, int resizeMode); -} diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index c30cc1736674..651089d6ce10 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -469,15 +469,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< /** * Update override configuration and recalculate full config. - * @see #mOverrideConfiguration + * @see #mRequestedOverrideConfiguration * @see #mFullConfiguration */ @Override - public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { + public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) { // We must diff before the configuration is applied so that we can capture the change // against the existing bounds. - final int diff = diffOverrideBounds(overrideConfiguration.windowConfiguration.getBounds()); - super.onOverrideConfigurationChanged(overrideConfiguration); + final int diff = diffRequestedOverrideBounds( + overrideConfiguration.windowConfiguration.getBounds()); + super.onRequestedOverrideConfigurationChanged(overrideConfiguration); if (mParent != null) { mParent.onDescendantOverrideConfigurationChanged(); } diff --git a/services/core/java/com/android/server/wm/WindowContainerController.java b/services/core/java/com/android/server/wm/WindowContainerController.java index 7cb89c5ae1fe..17bc0e2de1f8 100644 --- a/services/core/java/com/android/server/wm/WindowContainerController.java +++ b/services/core/java/com/android/server/wm/WindowContainerController.java @@ -72,12 +72,12 @@ class WindowContainerController<E extends WindowContainer, I extends WindowConta } @Override - public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { + public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) { synchronized (mGlobalLock) { if (mContainer == null) { return; } - mContainer.onOverrideConfigurationChanged(overrideConfiguration); + mContainer.onRequestedOverrideConfigurationChanged(overrideConfiguration); } } } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 2dec4dd13cbe..f7f7528c5234 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -770,8 +770,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } @Override - public void onOverrideConfigurationChanged(Configuration newOverrideConfig) { - super.onOverrideConfigurationChanged(newOverrideConfig); + public void onRequestedOverrideConfigurationChanged(Configuration newOverrideConfig) { + super.onRequestedOverrideConfigurationChanged(newOverrideConfig); updateConfiguration(); } @@ -924,7 +924,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } } pw.println(prefix + " Configuration=" + getConfiguration()); - pw.println(prefix + " OverrideConfiguration=" + getOverrideConfiguration()); + pw.println(prefix + " OverrideConfiguration=" + getRequestedOverrideConfiguration()); pw.println(prefix + " mLastReportedConfiguration=" + mLastReportedConfiguration); } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e115fed4db9a..d2dfa76d7b34 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2836,6 +2836,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP break; } } + cropRegionToStackBoundsIfNeeded(outRegion); if (forSurface) { if (mTouchableInsets != TOUCHABLE_INSETS_REGION) { diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index b85489a92464..6d7219173b32 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -126,7 +126,6 @@ cc_defaults { static_libs: [ "android.hardware.broadcastradio@common-utils-1x-lib", - "libscrypt_static", ], product_variables: { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 43d2dcf7e0d1..bf83ca912eed 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -107,6 +107,7 @@ static struct { jmethodID getLongPressTimeout; jmethodID getPointerLayer; jmethodID getPointerIcon; + jmethodID getPointerDisplayId; jmethodID getKeyboardLayoutOverlay; jmethodID getDeviceAlias; jmethodID getTouchCalibrationForInputDevice; @@ -174,15 +175,6 @@ static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, int32_t styl loadSystemIconAsSpriteWithPointerIcon(env, contextObj, style, &pointerIcon, outSpriteIcon); } -static void updatePointerControllerFromViewport( - sp<PointerController> controller, const DisplayViewport* const viewport) { - if (controller != nullptr && viewport != nullptr) { - const int32_t width = viewport->logicalRight - viewport->logicalLeft; - const int32_t height = viewport->logicalBottom - viewport->logicalTop; - controller->setDisplayViewport(width, height, viewport->orientation); - } -} - enum { WM_ACTION_PASS_TO_USER = 1, }; @@ -242,6 +234,7 @@ public: jfloatArray matrixArr); virtual TouchAffineTransformation getTouchAffineTransformation( const std::string& inputDeviceDescriptor, int32_t surfaceRotation); + virtual void updatePointerDisplay(); /* --- InputDispatcherPolicyInterface implementation --- */ @@ -314,10 +307,11 @@ private: std::atomic<bool> mInteractive; - void updateInactivityTimeoutLocked(const sp<PointerController>& controller); + void updateInactivityTimeoutLocked(); void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags); void ensureSpriteControllerLocked(); - + const DisplayViewport* findDisplayViewportLocked(int32_t displayId); + int32_t getPointerDisplayId(); static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); static inline JNIEnv* jniEnv() { @@ -391,9 +385,10 @@ bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const c return false; } -static const DisplayViewport* findInternalViewport(const std::vector<DisplayViewport>& viewports) { - for (const DisplayViewport& v : viewports) { - if (v.type == ViewportType::VIEWPORT_INTERNAL) { +const DisplayViewport* NativeInputManager::findDisplayViewportLocked(int32_t displayId) + REQUIRES(mLock) { + for (const DisplayViewport& v : mLocked.viewports) { + if (v.displayId == displayId) { return &v; } } @@ -420,20 +415,10 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO } } - const DisplayViewport* newInternalViewport = findInternalViewport(viewports); - { + { // acquire lock AutoMutex _l(mLock); - const DisplayViewport* oldInternalViewport = findInternalViewport(mLocked.viewports); - // Internal viewport has changed if there wasn't one earlier, and there is one now, or, - // if they are different. - const bool internalViewportChanged = (newInternalViewport != nullptr) && - (oldInternalViewport == nullptr || (*oldInternalViewport != *newInternalViewport)); - if (internalViewportChanged) { - sp<PointerController> controller = mLocked.pointerController.promote(); - updatePointerControllerFromViewport(controller, newInternalViewport); - } mLocked.viewports = viewports; - } + } // release lock mInputManager->getReader()->requestRefreshConfiguration( InputReaderConfiguration::CHANGE_DISPLAY_INFO); @@ -556,13 +541,41 @@ sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32 controller = new PointerController(this, mLooper, mLocked.spriteController); mLocked.pointerController = controller; + updateInactivityTimeoutLocked(); + } - const DisplayViewport* internalViewport = findInternalViewport(mLocked.viewports); - updatePointerControllerFromViewport(controller, internalViewport); + return controller; +} - updateInactivityTimeoutLocked(controller); +int32_t NativeInputManager::getPointerDisplayId() { + JNIEnv* env = jniEnv(); + jint pointerDisplayId = env->CallIntMethod(mServiceObj, + gServiceClassInfo.getPointerDisplayId); + if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) { + pointerDisplayId = ADISPLAY_ID_DEFAULT; + } + + return pointerDisplayId; +} + +void NativeInputManager::updatePointerDisplay() { + ATRACE_CALL(); + + jint pointerDisplayId = getPointerDisplayId(); + + AutoMutex _l(mLock); + sp<PointerController> controller = mLocked.pointerController.promote(); + if (controller != nullptr) { + const DisplayViewport* viewport = findDisplayViewportLocked(pointerDisplayId); + if (viewport == nullptr) { + ALOGW("Can't find pointer display viewport, fallback to default display."); + viewport = findDisplayViewportLocked(ADISPLAY_ID_DEFAULT); + } + + if (viewport != nullptr) { + controller->setDisplayViewport(*viewport); + } } - return controller; } void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) { @@ -821,16 +834,16 @@ void NativeInputManager::setSystemUiVisibility(int32_t visibility) { if (mLocked.systemUiVisibility != visibility) { mLocked.systemUiVisibility = visibility; - - sp<PointerController> controller = mLocked.pointerController.promote(); - if (controller != nullptr) { - updateInactivityTimeoutLocked(controller); - } + updateInactivityTimeoutLocked(); } } -void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller) - REQUIRES(mLock) { +void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) { + sp<PointerController> controller = mLocked.pointerController.promote(); + if (controller == nullptr) { + return; + } + bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN; controller->setInactivityTimeout(lightsOut ? PointerController::INACTIVITY_TIMEOUT_SHORT @@ -1824,6 +1837,9 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz, "getPointerIcon", "()Landroid/view/PointerIcon;"); + GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz, + "getPointerDisplayId", "()I"); + GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz, "getKeyboardLayoutOverlay", "(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;"); diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 4d0556c7507a..0e349b7893e7 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -756,14 +756,18 @@ struct GnssMeasurementCallback : public IGnssMeasurementCallback_V2_0 { Return<void> gnssMeasurementCb(const IGnssMeasurementCallback_V1_1::GnssData& data) override; Return<void> GnssMeasurementCb(const IGnssMeasurementCallback_V1_0::GnssData& data) override; private: - void translateGnssMeasurement_V1_0( - const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurement, - JavaObject& object); - jobjectArray translateGnssMeasurements( - JNIEnv* env, - const IGnssMeasurementCallback_V1_1::GnssMeasurement* measurements_v1_1, - const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurements_v1_0, - size_t count); + template<class T> + void translateSingleGnssMeasurement(const T* measurement, JavaObject& object); + + template<class T> + jobjectArray translateAllGnssMeasurements(JNIEnv* env, const T* measurements, size_t count); + + template<class T> + void translateAndSetGnssData(const T& data); + + template<class T> + size_t getMeasurementCount(const T& data); + jobject translateGnssClock( JNIEnv* env, const IGnssMeasurementCallback_V1_0::GnssClock* clock); void setMeasurementData(JNIEnv* env, jobject clock, jobjectArray measurementArray); @@ -777,41 +781,48 @@ Return<void> GnssMeasurementCallback::gnssMeasurementCb_2_0( Return<void> GnssMeasurementCallback::gnssMeasurementCb( const IGnssMeasurementCallback_V1_1::GnssData& data) { - JNIEnv* env = getJniEnv(); - - jobject clock; - jobjectArray measurementArray; - - clock = translateGnssClock(env, &data.clock); - - measurementArray = translateGnssMeasurements( - env, data.measurements.data(), nullptr, data.measurements.size()); - setMeasurementData(env, clock, measurementArray); - - env->DeleteLocalRef(clock); - env->DeleteLocalRef(measurementArray); + translateAndSetGnssData(data); return Void(); } Return<void> GnssMeasurementCallback::GnssMeasurementCb( const IGnssMeasurementCallback_V1_0::GnssData& data) { + translateAndSetGnssData(data); + return Void(); +} + +template<class T> +void GnssMeasurementCallback::translateAndSetGnssData(const T& data) { JNIEnv* env = getJniEnv(); jobject clock; jobjectArray measurementArray; clock = translateGnssClock(env, &data.clock); - measurementArray = translateGnssMeasurements( - env, nullptr, data.measurements.data(), data.measurementCount); + size_t count = getMeasurementCount(data); + measurementArray = translateAllGnssMeasurements(env, data.measurements.data(), count); setMeasurementData(env, clock, measurementArray); env->DeleteLocalRef(clock); env->DeleteLocalRef(measurementArray); - return Void(); } -// preallocate object as: JavaObject object(env, "android/location/GnssMeasurement"); -void GnssMeasurementCallback::translateGnssMeasurement_V1_0( +template<> +size_t GnssMeasurementCallback::getMeasurementCount<IGnssMeasurementCallback_V1_0::GnssData> + (const IGnssMeasurementCallback_V1_0::GnssData& data) { + return data.measurementCount; +} + +template<> +size_t GnssMeasurementCallback::getMeasurementCount<IGnssMeasurementCallback_V1_1::GnssData> + (const IGnssMeasurementCallback_V1_1::GnssData& data) { + return data.measurements.size(); +} + +// Preallocate object as: JavaObject object(env, "android/location/GnssMeasurement"); +template<> +void GnssMeasurementCallback::translateSingleGnssMeasurement + <IGnssMeasurementCallback_V1_0::GnssMeasurement>( const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurement, JavaObject& object) { uint32_t flags = static_cast<uint32_t>(measurement->flags); @@ -852,6 +863,20 @@ void GnssMeasurementCallback::translateGnssMeasurement_V1_0( } } +// Preallocate object as: JavaObject object(env, "android/location/GnssMeasurement"); +template<> +void GnssMeasurementCallback::translateSingleGnssMeasurement + <IGnssMeasurementCallback_V1_1::GnssMeasurement>( + const IGnssMeasurementCallback_V1_1::GnssMeasurement* measurement_V1_1, + JavaObject& object) { + translateSingleGnssMeasurement(&(measurement_V1_1->v1_0), object); + + // Set the V1_1 flag, and mark that new field has valid information for Java Layer + SET(AccumulatedDeltaRangeState, + (static_cast<int32_t>(measurement_V1_1->accumulatedDeltaRangeState) | + ADR_STATE_HALF_CYCLE_REPORTED)); +} + jobject GnssMeasurementCallback::translateGnssClock( JNIEnv* env, const IGnssMeasurementCallback_V1_0::GnssClock* clock) { JavaObject object(env, "android/location/GnssClock"); @@ -891,10 +916,10 @@ jobject GnssMeasurementCallback::translateGnssClock( return object.get(); } -jobjectArray GnssMeasurementCallback::translateGnssMeasurements(JNIEnv* env, - const IGnssMeasurementCallback_V1_1::GnssMeasurement* measurements_v1_1, - const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurements_v1_0, - size_t count) { +template<class T> +jobjectArray GnssMeasurementCallback::translateAllGnssMeasurements(JNIEnv* env, + const T* measurements, + size_t count) { if (count == 0) { return nullptr; } @@ -907,17 +932,7 @@ jobjectArray GnssMeasurementCallback::translateGnssMeasurements(JNIEnv* env, for (uint16_t i = 0; i < count; ++i) { JavaObject object(env, "android/location/GnssMeasurement"); - if (measurements_v1_1 != nullptr) { - translateGnssMeasurement_V1_0(&(measurements_v1_1[i].v1_0), object); - - // Set the V1_1 flag, and mark that new field has valid information for Java Layer - SET(AccumulatedDeltaRangeState, - (static_cast<int32_t>(measurements_v1_1[i].accumulatedDeltaRangeState) | - ADR_STATE_HALF_CYCLE_REPORTED)); - } else { - translateGnssMeasurement_V1_0(&(measurements_v1_0[i]), object); - } - + translateSingleGnssMeasurement(&(measurements[i]), object); env->SetObjectArrayElement(gnssMeasurementArray, i, object.get()); } diff --git a/services/core/jni/com_android_server_locksettings_SyntheticPasswordManager.cpp b/services/core/jni/com_android_server_locksettings_SyntheticPasswordManager.cpp index bc13fdec6cbc..9dd6032497da 100644 --- a/services/core/jni/com_android_server_locksettings_SyntheticPasswordManager.cpp +++ b/services/core/jni/com_android_server_locksettings_SyntheticPasswordManager.cpp @@ -27,10 +27,6 @@ #include <gatekeeper/password_handle.h> -extern "C" { -#include "crypto_scrypt.h" -} - namespace android { static jlong android_server_SyntheticPasswordManager_nativeSidFromPasswordHandle(JNIEnv* env, jobject, jbyteArray handleArray) { @@ -48,38 +44,9 @@ static jlong android_server_SyntheticPasswordManager_nativeSidFromPasswordHandle } } -static jbyteArray android_server_SyntheticPasswordManager_nativeScrypt(JNIEnv* env, jobject, jbyteArray password, jbyteArray salt, jint N, jint r, jint p, jint outLen) { - if (!password || !salt) { - return NULL; - } - - int passwordLen = env->GetArrayLength(password); - int saltLen = env->GetArrayLength(salt); - jbyteArray ret = env->NewByteArray(outLen); - - jbyte* passwordPtr = (jbyte*)env->GetByteArrayElements(password, NULL); - jbyte* saltPtr = (jbyte*)env->GetByteArrayElements(salt, NULL); - jbyte* retPtr = (jbyte*)env->GetByteArrayElements(ret, NULL); - - int rc = crypto_scrypt((const uint8_t *)passwordPtr, passwordLen, - (const uint8_t *)saltPtr, saltLen, N, r, p, (uint8_t *)retPtr, - outLen); - env->ReleaseByteArrayElements(password, passwordPtr, JNI_ABORT); - env->ReleaseByteArrayElements(salt, saltPtr, JNI_ABORT); - env->ReleaseByteArrayElements(ret, retPtr, 0); - - if (!rc) { - return ret; - } else { - SLOGE("scrypt failed"); - return NULL; - } -} - static const JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"nativeSidFromPasswordHandle", "([B)J", (void*)android_server_SyntheticPasswordManager_nativeSidFromPasswordHandle}, - {"nativeScrypt", "([B[BIIII)[B", (void*)android_server_SyntheticPasswordManager_nativeScrypt}, }; int register_android_server_SyntheticPasswordManager(JNIEnv* env) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 65d32450b27f..d8225b38487c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -81,7 +81,8 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { } @Override - public void setGlobalPrivateDns(ComponentName who, int mode, String privateDnsHost) { + public int setGlobalPrivateDns(ComponentName who, int mode, String privateDnsHost) { + return DevicePolicyManager.PRIVATE_DNS_SET_ERROR_FAILURE_SETTING; } @Override @@ -98,6 +99,11 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public void grantDeviceIdsAccessToProfileOwner(ComponentName who, int userId) { } @Override + public int getPasswordComplexity() { + return DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; + } + + @Override public void installUpdateFromFile(ComponentName admin, ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {} @@ -125,4 +131,20 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public List<String> getCrossProfileCalendarPackagesForUser(int userHandle) { return Collections.emptyList(); } + + @Override + public boolean isManagedKiosk() { + return false; + } + + @Override + public boolean isUnattendedManagedKiosk() { + return false; + } + + @Override + public boolean startViewCalendarEventInManagedProfile(String packageName, long eventId, + long start, long end, boolean allDay, int flags) { + return false; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f68f4d7424f4..ab27d21dc565 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -17,6 +17,7 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.BIND_DEVICE_ADMIN; +import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; @@ -40,10 +41,13 @@ import static android.app.admin.DevicePolicyManager.CODE_USER_SETUP_COMPLETED; import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS; import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL; import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL; +import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_SELECTION; import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP; import static android.app.admin.DevicePolicyManager.DELEGATION_INSTALL_EXISTING_PACKAGE; import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES; +import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING; import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS; +import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_INSTALLATION; import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT; import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; @@ -53,12 +57,15 @@ import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLE import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_UNKNOWN; +import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_ERROR_FAILURE_SETTING; +import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_SUCCESS; import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; @@ -109,7 +116,9 @@ import android.app.StatusBarManager; import android.app.admin.DeviceAdminInfo; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyCache; +import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.NetworkEvent; import android.app.admin.PasswordMetrics; @@ -121,6 +130,7 @@ import android.app.admin.SystemUpdatePolicy; import android.app.backup.IBackupManager; import android.app.trust.TrustManager; import android.app.usage.UsageStatsManagerInternal; +import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentValues; @@ -181,6 +191,7 @@ import android.os.UserManager; import android.os.UserManagerInternal; import android.os.UserManagerInternal.UserRestrictionsListener; import android.os.storage.StorageManager; +import android.provider.CalendarContract; import android.provider.ContactsContract.QuickContact; import android.provider.ContactsInternal; import android.provider.Settings; @@ -195,6 +206,7 @@ import android.security.keystore.AttestationUtils; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.ParcelableKeyGenParameterSpec; import android.service.persistentdata.PersistentDataBlockManager; +import android.stats.devicepolicy.DevicePolicyEnums; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.text.TextUtils; @@ -205,7 +217,6 @@ import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; -import android.util.StatsLog; import android.util.Xml; import android.view.IWindowManager; import android.view.accessibility.AccessibilityManager; @@ -358,9 +369,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { DELEGATION_PACKAGE_ACCESS, DELEGATION_PERMISSION_GRANT, DELEGATION_INSTALL_EXISTING_PACKAGE, - DELEGATION_KEEP_UNINSTALLED_PACKAGES + DELEGATION_KEEP_UNINSTALLED_PACKAGES, + DELEGATION_NETWORK_LOGGING, + DELEGATION_CERT_SELECTION, + DELEGATION_PACKAGE_INSTALLATION }; + // 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, + DELEGATION_PACKAGE_INSTALLATION + }); + + // 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[] { + DELEGATION_NETWORK_LOGGING, + DELEGATION_CERT_SELECTION, + }); + /** * System property whose value is either "true" or "false", indicating whether * device owner is present. @@ -445,6 +471,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = TimeUnit.HOURS.toMillis(1); /** + * The amount of ms that a managed kiosk must go without user interaction to be considered + * unattended. + */ + private static final int UNATTENDED_MANAGED_KIOSK_MS = 30000; + + /** * Strings logged with {@link * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}. */ @@ -4051,6 +4083,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } maybeLogPasswordComplexitySet(who, userId, parent, metrics); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PASSWORD_QUALITY) + .setAdmin(who) + .setInt(quality) + .setBoolean(parent) + .write(); } /** @@ -4164,6 +4202,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } maybeLogPasswordComplexitySet(who, userId, parent, metrics); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_LENGTH) + .setAdmin(who) + .setInt(length) + .write(); } @Override @@ -4391,6 +4434,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } maybeLogPasswordComplexitySet(who, userId, parent, metrics); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_UPPER_CASE) + .setAdmin(who) + .setInt(length) + .write(); } @Override @@ -4414,6 +4462,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } maybeLogPasswordComplexitySet(who, userId, parent, metrics); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_LOWER_CASE) + .setAdmin(who) + .setInt(length) + .write(); } @Override @@ -4440,6 +4493,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } maybeLogPasswordComplexitySet(who, userId, parent, metrics); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_LETTERS) + .setAdmin(who) + .setInt(length) + .write(); } @Override @@ -4466,6 +4524,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } maybeLogPasswordComplexitySet(who, userId, parent, metrics); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_NUMERIC) + .setAdmin(who) + .setInt(length) + .write(); } @Override @@ -4492,6 +4555,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } maybeLogPasswordComplexitySet(who, userId, parent, metrics); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_SYMBOLS) + .setAdmin(who) + .setInt(length) + .write(); } @Override @@ -4518,6 +4586,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } maybeLogPasswordComplexitySet(who, userId, parent, metrics); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_NON_LETTER) + .setAdmin(who) + .setInt(length) + .write(); } @Override @@ -4664,6 +4737,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + @PasswordComplexity + public int getPasswordComplexity() { + final int callingUserId = mInjector.userHandleGetCallingUserId(); + enforceUserUnlocked(callingUserId); + mContext.enforceCallingOrSelfPermission( + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY, + "Must have " + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY + " permission."); + + synchronized (getLockObject()) { + int targetUserId = getCredentialOwner(callingUserId, /* parent= */ false); + PasswordMetrics metrics = getUserPasswordMetricsLocked(targetUserId); + return metrics == null ? PASSWORD_COMPLEXITY_NONE : metrics.determineComplexity(); + } + } + + @Override public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) { enforceFullCrossUsersPermission(userHandle); synchronized (getLockObject()) { @@ -4890,14 +4979,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private boolean resetPasswordInternal(String password, long tokenHandle, byte[] token, int flags, int callingUid, int userHandle) { int quality; + final int realQuality; synchronized (getLockObject()) { quality = getPasswordQuality(null, userHandle, /* parent */ false); if (quality == DevicePolicyManager.PASSWORD_QUALITY_MANAGED) { quality = PASSWORD_QUALITY_UNSPECIFIED; } final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password); + realQuality = metrics.quality; if (quality != PASSWORD_QUALITY_UNSPECIFIED) { - final int realQuality = metrics.quality; + if (realQuality < quality && quality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) { Slog.w(LOG_TAG, "resetPassword: password quality 0x" @@ -4984,7 +5075,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { try { if (token == null) { if (!TextUtils.isEmpty(password)) { - mLockPatternUtils.saveLockPassword(password, null, quality, userHandle); + mLockPatternUtils.saveLockPassword(password, null, realQuality, userHandle); } else { mLockPatternUtils.clearLock(null, userHandle); } @@ -4993,7 +5084,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { result = mLockPatternUtils.setLockCredentialWithToken(password, TextUtils.isEmpty(password) ? LockPatternUtils.CREDENTIAL_TYPE_NONE : LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, - quality, tokenHandle, token, userHandle); + realQuality, tokenHandle, token, userHandle); } boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0; if (requireEntry) { @@ -5094,7 +5185,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(ident); } - mInjector.getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin( + getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin( UserHandle.USER_SYSTEM, timeMs); } @@ -5113,7 +5204,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } policy.mLastMaximumTimeToLock = timeMs; - mInjector.getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin( + getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin( userId, policy.mLastMaximumTimeToLock); } @@ -5285,6 +5376,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(ident); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.LOCK_NOW) + .setInt(flags) + .write(); } @Override @@ -5363,6 +5458,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long id = mInjector.binderClearCallingIdentity(); try { alias = mCertificateMonitor.installCaCert(userHandle, certBuffer); + final boolean isDelegate = (admin == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.INSTALL_CA_CERT) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .write(); if (alias == null) { Log.w(LOG_TAG, "Problem installing cert"); return false; @@ -5422,6 +5523,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { keyChain.setGrant(callingUid, alias, true); } keyChain.setUserSelectable(alias, isUserSelectable); + final boolean isDelegate = (who == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.INSTALL_KEY_PAIR) + .setAdmin(callerPackage) + .setBoolean(isDelegate) + .write(); return true; } catch (RemoteException e) { Log.e(LOG_TAG, "Installing certificate", e); @@ -5697,13 +5804,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Intent intent = new Intent(DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS); - intent.setComponent(aliasChooser); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, uid); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URI, uri); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS, alias); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE, response); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final ComponentName delegateReceiver; + delegateReceiver = resolveDelegateReceiver(DELEGATION_CERT_SELECTION, + DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS, caller.getIdentifier()); + + if (delegateReceiver != null) { + intent.setComponent(delegateReceiver); + } else { + intent.setComponent(aliasChooser); + } + final long id = mInjector.binderClearCallingIdentity(); try { mContext.sendOrderedBroadcastAsUser(intent, caller, null, new BroadcastReceiver() { @@ -5763,22 +5879,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public void setDelegatedScopes(ComponentName who, String delegatePackage, - List<String> scopes) throws SecurityException { + List<String> scopeList) throws SecurityException { Preconditions.checkNotNull(who, "ComponentName is null"); Preconditions.checkStringNotEmpty(delegatePackage, "Delegate package is null or empty"); - Preconditions.checkCollectionElementsNotNull(scopes, "Scopes"); + Preconditions.checkCollectionElementsNotNull(scopeList, "Scopes"); // Remove possible duplicates. - scopes = new ArrayList(new ArraySet(scopes)); + final ArrayList<String> scopes = new ArrayList(new ArraySet(scopeList)); // Ensure given scopes are valid. if (scopes.retainAll(Arrays.asList(DELEGATIONS))) { throw new IllegalArgumentException("Unexpected delegation scopes"); } - + final boolean hasDoDelegation = !Collections.disjoint(scopes, DEVICE_OWNER_DELEGATIONS); // Retrieve the user ID of the calling process. final int userId = mInjector.userHandleGetCallingUserId(); synchronized (getLockObject()) { // Ensure calling process is device/profile owner. - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (hasDoDelegation) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } else { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + } // Ensure the delegate is installed (skip this for DELEGATION_CERT_INSTALL in pre-N). if (shouldCheckIfDelegatePackageIsInstalled(delegatePackage, getTargetSdk(who.getPackageName(), userId), scopes)) { @@ -5791,31 +5911,57 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Set the new delegate in user policies. final DevicePolicyData policy = getUserData(userId); + List<String> exclusiveScopes = null; if (!scopes.isEmpty()) { policy.mDelegationMap.put(delegatePackage, new ArrayList<>(scopes)); + exclusiveScopes = new ArrayList<>(scopes); + exclusiveScopes.retainAll(EXCLUSIVE_DELEGATIONS); } else { // Remove any delegation info if the given scopes list is empty. policy.mDelegationMap.remove(delegatePackage); } - - // Notify delegate package of updates. - final Intent intent = new Intent( - DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED); - // Only call receivers registered with Context#registerReceiver (don’t wake delegate). - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - // Limit components this intent resolves to to the delegate package. - intent.setPackage(delegatePackage); - // Include the list of delegated scopes as an extra. - intent.putStringArrayListExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, - (ArrayList<String>) scopes); - // Send the broadcast. - mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); - + sendDelegationChangedBroadcast(delegatePackage, scopes, userId); + + // If set, remove exclusive scopes from all other delegates + if (exclusiveScopes != null && !exclusiveScopes.isEmpty()) { + for (Map.Entry<String, List<String>> entry : policy.mDelegationMap.entrySet()) { + final String currentPackage = entry.getKey(); + final List<String> currentScopes = entry.getValue(); + + if (!currentPackage.equals(delegatePackage)) { + // Iterate through all other delegates + if (currentScopes.removeAll(exclusiveScopes)) { + // And if this delegate had some exclusive scopes which are now moved + // to the new delegate, notify about its delegation changes. + if (currentScopes.isEmpty()) { + policy.mDelegationMap.remove(currentPackage); + } + sendDelegationChangedBroadcast(currentPackage, + new ArrayList<>(currentScopes), userId); + } + } + } + } // Persist updates. saveSettingsLocked(userId); } } + private void sendDelegationChangedBroadcast(String delegatePackage, ArrayList<String> scopes, + int userId) { + // Notify delegate package of updates. + final Intent intent = new Intent( + DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED); + // Only call receivers registered with Context#registerReceiver (don’t wake delegate). + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + // Limit components this intent resolves to to the delegate package. + intent.setPackage(delegatePackage); + // Include the list of delegated scopes as an extra. + intent.putStringArrayListExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, scopes); + // Send the broadcast. + mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); + } + /** * Get the delegation scopes given to a delegate package by a device owner or profile owner. * @@ -5883,17 +6029,59 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { // Ensure calling process is device/profile owner. getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - final DevicePolicyData policy = getUserData(userId); + return getDelegatePackagesInternalLocked(scope, userId); + } + } - // Create a list to hold the resulting delegate packages. - final List<String> delegatePackagesWithScope = new ArrayList<>(); - // Add all delegations containing scope to the result list. - for (int i = 0; i < policy.mDelegationMap.size(); i++) { - if (policy.mDelegationMap.valueAt(i).contains(scope)) { - delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i)); - } + private List<String> getDelegatePackagesInternalLocked(String scope, int userId) { + final DevicePolicyData policy = getUserData(userId); + + // Create a list to hold the resulting delegate packages. + final List<String> delegatePackagesWithScope = new ArrayList<>(); + // Add all delegations containing scope to the result list. + for (int i = 0; i < policy.mDelegationMap.size(); i++) { + if (policy.mDelegationMap.valueAt(i).contains(scope)) { + delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i)); + } + } + return delegatePackagesWithScope; + } + + /** + * Return the ComponentName of the receiver that handles the given broadcast action, from + * the app that holds the given delegation capability. If the app defines multiple receivers + * with the same intent action filter, will return any one of them nondeterministically. + * + * @return ComponentName of the receiver or {@null} if none exists. + */ + private ComponentName resolveDelegateReceiver(String scope, String action, int userId) { + + final List<String> delegates; + synchronized (getLockObject()) { + delegates = getDelegatePackagesInternalLocked(scope, userId); + } + if (delegates.size() != 1) { + Slog.wtf(LOG_TAG, "More than one delegate holds " + scope); + return null; + } + final String pkg = delegates.get(0); + Intent intent = new Intent(action); + intent.setPackage(pkg); + final List<ResolveInfo> receivers; + try { + receivers = mIPackageManager.queryIntentReceivers( + intent, null, 0, userId).getList(); + } catch (RemoteException e) { + return null; + } + final int count = receivers.size(); + if (count >= 1) { + if (count > 1) { + Slog.w(LOG_TAG, pkg + " defines more than one delegate receiver for " + action); } - return delegatePackagesWithScope; + return receivers.get(0).activityInfo.getComponentName(); + } else { + return null; } } @@ -5956,15 +6144,34 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ private void enforceCanManageScope(ComponentName who, String callerPackage, int reqPolicy, String scope) { + enforceCanManageScopeOrCheckPermission(who, callerPackage, reqPolicy, scope, null); + } + + /** + * Throw a security exception if a ComponentName is given and it is not a device/profile owner + * OR if the calling process is not a delegate of the given scope and does not hold the + * required permission. + */ + private void enforceCanManageScopeOrCheckPermission(@Nullable ComponentName who, + @NonNull String callerPackage, int reqPolicy, @NonNull String scope, + @Nullable String permission) { // If a ComponentName is given ensure it is a device or profile owner according to policy. if (who != null) { synchronized (getLockObject()) { getActiveAdminForCallerLocked(who, reqPolicy); } - // If no ComponentName is given ensure calling process has scope delegation. - } else if (!isCallerDelegate(callerPackage, scope)) { - throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() - + " is not a delegate of scope " + scope + "."); + } else { + // If no ComponentName is given ensure calling process has scope delegation or required + // permission + if (isCallerDelegate(callerPackage, scope)) { + return; + } + if (permission == null) { + throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() + + " is not a delegate of scope " + scope + "."); + } else { + mContext.enforceCallingOrSelfPermission(permission, null); + } } } @@ -6116,6 +6323,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { admin = getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.WIPE_DATA_WITH_REASON) + .setAdmin(admin.info.getComponent()) + .setInt(flags) + .write(); String internalReason = "DevicePolicyManager.wipeDataWithReason() from " + admin.info.getComponent().flattenToShortString(); wipeDataNoLock( @@ -6898,9 +7110,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) throws SecurityException { + private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) + throws SecurityException { synchronized (getLockObject()) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } + ensureAllUsersAffiliated(); + } + + private void ensureAllUsersAffiliated() throws SecurityException { + synchronized (getLockObject()) { if (!areAllUsersAffiliatedWithDeviceLocked()) { throw new SecurityException("Not all users are affiliated."); } @@ -6959,14 +7178,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } void sendDeviceOwnerCommand(String action, Bundle extras) { - int deviceOwnerUserId; - ComponentName deviceOwnerComponent; + final int deviceOwnerUserId; synchronized (getLockObject()) { deviceOwnerUserId = mOwners.getDeviceOwnerUserId(); - deviceOwnerComponent = mOwners.getDeviceOwnerComponent(); } - sendActiveAdminCommand(action, extras, deviceOwnerUserId, - deviceOwnerComponent); + + ComponentName receiverComponent = null; + if (action.equals(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE)) { + receiverComponent = resolveDelegateReceiver(DELEGATION_NETWORK_LOGGING, action, + deviceOwnerUserId); + } + if (receiverComponent == null) { + synchronized (getLockObject()) { + receiverComponent = mOwners.getDeviceOwnerComponent(); + } + } + sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent); } private void sendProfileOwnerCommand(String action, Bundle extras, int userHandle) { @@ -7220,6 +7447,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISABLED_FEATURES_SET, who.getPackageName(), userHandle, affectedUserId, which); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_KEYGUARD_DISABLED_FEATURES) + .setAdmin(who) + .setInt(which) + .setBoolean(parent) + .write(); } /** @@ -9466,7 +9699,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } saveUserRestrictionsLocked(userHandle); } - StatsLog.write(StatsLog.USER_RESTRICTION_CHANGED, key, enabledFromThisOwner); + final int eventId = enabledFromThisOwner + ? DevicePolicyEnums.ADD_USER_RESTRICTION + : DevicePolicyEnums.REMOVE_USER_RESTRICTION; + DevicePolicyEventLogger + .createEvent(eventId) + .setAdmin(who) + .setStrings(key) + .write(); if (SecurityLog.isLoggingEnabled()) { final int eventTag = enabledFromThisOwner ? SecurityLog.TAG_USER_RESTRICTION_ADDED @@ -10238,6 +10478,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { try { setUserRestriction(who, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, (Integer.parseInt(value) == 0) ? true : false); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_SECURE_SETTING) + .setAdmin(who) + .setStrings(setting, value) + .write(); } catch (NumberFormatException exc) { Slog.e(LOG_TAG, "Invalid value: " + value + " for setting " + setting); } @@ -10265,6 +10510,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(id); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_SECURE_SETTING) + .setAdmin(who) + .setStrings(setting, value) + .write(); } @Override @@ -10623,6 +10873,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { updateMaximumTimeToLockLocked(userId); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SEPARATE_PROFILE_CHALLENGE_CHANGED) + .setBoolean(isSeparateProfileChallengeEnabled(userId)) + .write(); } @Override @@ -10963,6 +11217,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(userId); } } + final boolean isDelegate = (admin == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PERMISSION_POLICY) + .setAdmin(callerPackage) + .setInt(policy) + .setBoolean(isDelegate) + .write(); } @Override @@ -11014,7 +11275,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { PackageManager.FLAG_PERMISSION_POLICY_FIXED, 0, user); } break; } - return true; } catch (SecurityException se) { return false; } catch (NameNotFoundException e) { @@ -11023,6 +11283,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(ident); } } + final boolean isDelegate = (admin == null); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PERMISSION_GRANT_STATE) + .setAdmin(callerPackage) + .setStrings(permission) + .setInt(grantState) + .setBoolean(isDelegate) + .write(); + return true; } @Override @@ -11848,6 +12117,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mSecurityLogMonitor.stop(); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_SECURITY_LOGGING_ENABLED) + .setAdmin(admin) + .setBoolean(enabled) + .write(); } @Override @@ -11885,13 +12159,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkNotNull(admin); ensureDeviceOwnerAndAllUsersAffiliated(admin); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.RETRIEVE_PRE_REBOOT_SECURITY_LOGS) + .setAdmin(admin) + .write(); + if (!mContext.getResources().getBoolean(R.bool.config_supportPreRebootSecurityLogs) || !mInjector.securityLogGetLoggingEnabledProperty()) { return null; } recordSecurityLogRetrievalTime(); - ArrayList<SecurityEvent> output = new ArrayList<SecurityEvent>(); try { SecurityLog.readPreviousEvents(output); @@ -11918,6 +12196,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { recordSecurityLogRetrievalTime(); List<SecurityEvent> logs = mSecurityLogMonitor.retrieveLogs(); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.RETRIEVE_SECURITY_LOGS) + .setAdmin(admin) + .write(); return logs != null ? new ParceledListSlice<SecurityEvent>(logs) : null; } @@ -12379,13 +12661,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public void setNetworkLoggingEnabled(ComponentName admin, boolean enabled) { + public void setNetworkLoggingEnabled(@Nullable ComponentName admin, + @NonNull String packageName, boolean enabled) { if (!mHasFeature) { return; } synchronized (getLockObject()) { - Preconditions.checkNotNull(admin); - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + enforceCanManageScope(admin, packageName, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, + DELEGATION_NETWORK_LOGGING); if (enabled == isNetworkLoggingEnabledInternalLocked()) { // already in the requested state @@ -12486,12 +12769,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public boolean isNetworkLoggingEnabled(ComponentName admin) { + public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin, + @NonNull String packageName) { if (!mHasFeature) { return false; } synchronized (getLockObject()) { - enforceDeviceOwnerOrManageUsers(); + enforceCanManageScopeOrCheckPermission(admin, packageName, + DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, DELEGATION_NETWORK_LOGGING, + android.Manifest.permission.MANAGE_USERS); return isNetworkLoggingEnabledInternalLocked(); } } @@ -12509,12 +12795,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * @see NetworkLoggingHandler#MAX_EVENTS_PER_BATCH */ @Override - public List<NetworkEvent> retrieveNetworkLogs(ComponentName admin, long batchToken) { + public List<NetworkEvent> retrieveNetworkLogs(@Nullable ComponentName admin, + @NonNull String packageName, long batchToken) { if (!mHasFeature) { return null; } - Preconditions.checkNotNull(admin); - ensureDeviceOwnerAndAllUsersAffiliated(admin); + enforceCanManageScope(admin, packageName, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, + DELEGATION_NETWORK_LOGGING); + ensureAllUsersAffiliated(); synchronized (getLockObject()) { if (mNetworkLogger == null @@ -13278,32 +13566,40 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public void setGlobalPrivateDns(@NonNull ComponentName who, int mode, String privateDnsHost) { + public int setGlobalPrivateDns(@NonNull ComponentName who, int mode, String privateDnsHost) { if (!mHasFeature) { - return; + return PRIVATE_DNS_SET_ERROR_FAILURE_SETTING; } Preconditions.checkNotNull(who, "ComponentName is null"); enforceDeviceOwner(who); + final int returnCode; + switch (mode) { case PRIVATE_DNS_MODE_OPPORTUNISTIC: if (!TextUtils.isEmpty(privateDnsHost)) { - throw new IllegalArgumentException("A DNS host should not be provided when " + - "setting opportunistic mode."); + throw new IllegalArgumentException( + "Host provided for opportunistic mode, but is not needed."); } putPrivateDnsSettings(ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC, null); - break; + return PRIVATE_DNS_SET_SUCCESS; case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME: - if (!NetworkUtils.isWeaklyValidatedHostname(privateDnsHost)) { + if (TextUtils.isEmpty(privateDnsHost) + || !NetworkUtils.isWeaklyValidatedHostname(privateDnsHost)) { throw new IllegalArgumentException( - String.format("Provided hostname is not valid: %s", privateDnsHost)); + String.format("Provided hostname %s is not valid", privateDnsHost)); } - putPrivateDnsSettings(ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, + + // Connectivity check will have been performed in the DevicePolicyManager before + // the call here. + putPrivateDnsSettings( + ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, privateDnsHost); - break; + return PRIVATE_DNS_SET_SUCCESS; default: - throw new IllegalArgumentException(String.format("Unsupported mode: %d", mode)); + throw new IllegalArgumentException( + String.format("Provided mode, %d, is not a valid mode.", mode)); } } @@ -13447,4 +13743,134 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } return Collections.emptyList(); } + + @Override + public boolean isManagedKiosk() { + if (!mHasFeature) { + return false; + } + enforceManageUsers(); + long id = mInjector.binderClearCallingIdentity(); + try { + return isManagedKioskInternal(); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } finally { + mInjector.binderRestoreCallingIdentity(id); + } + } + + @Override + public boolean isUnattendedManagedKiosk() { + if (!mHasFeature) { + return false; + } + enforceManageUsers(); + long id = mInjector.binderClearCallingIdentity(); + try { + return isManagedKioskInternal() + && getPowerManagerInternal().wasDeviceIdleFor(UNATTENDED_MANAGED_KIOSK_MS); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } finally { + mInjector.binderRestoreCallingIdentity(id); + } + } + + /** + * Returns whether the device is currently being used as a publicly-accessible dedicated device. + * Assumes that feature checks and permission checks have already been performed, and that the + * calling identity has been cleared. + */ + private boolean isManagedKioskInternal() throws RemoteException { + return mOwners.hasDeviceOwner() + && mInjector.getIActivityManager().getLockTaskModeState() + == ActivityManager.LOCK_TASK_MODE_LOCKED + && !isLockTaskFeatureEnabled(DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO) + && !deviceHasKeyguard() + && !inEphemeralUserSession(); + } + + private boolean isLockTaskFeatureEnabled(int lockTaskFeature) throws RemoteException { + int lockTaskFeatures = + getUserData(mInjector.getIActivityManager().getCurrentUser().id).mLockTaskFeatures; + return (lockTaskFeatures & lockTaskFeature) == lockTaskFeature; + } + + private boolean deviceHasKeyguard() { + for (UserInfo userInfo : mUserManager.getUsers()) { + if (mLockPatternUtils.isSecure(userInfo.id)) { + return true; + } + } + return false; + } + + private boolean inEphemeralUserSession() { + for (UserInfo userInfo : mUserManager.getUsers()) { + if (mInjector.getUserManager().isUserEphemeral(userInfo.id)) { + return true; + } + } + return false; + } + + private PowerManagerInternal getPowerManagerInternal() { + return mInjector.getPowerManagerInternal(); + } + + @Override + public boolean startViewCalendarEventInManagedProfile(String packageName, long eventId, + long start, long end, boolean allDay, int flags) { + if (!mHasFeature) { + return false; + } + Preconditions.checkStringNotEmpty(packageName, "Package name is empty"); + + final int callingUid = mInjector.binderGetCallingUid(); + final int callingUserId = mInjector.userHandleGetCallingUserId(); + if (!isCallingFromPackage(packageName, callingUid)) { + throw new SecurityException("Input package name doesn't align with actual " + + "calling package."); + } + final long identity = mInjector.binderClearCallingIdentity(); + try { + final int workProfileUserId = getManagedUserId(callingUserId); + if (workProfileUserId < 0) { + return false; + } + if (!isPackageAllowedToAccessCalendarForUser(packageName, workProfileUserId)) { + Log.d(LOG_TAG, String.format("Package %s is not allowed to access cross-profile" + + "calendar APIs", packageName)); + return false; + } + final Intent intent = new Intent(CalendarContract.ACTION_VIEW_WORK_CALENDAR_EVENT); + intent.setPackage(packageName); + intent.putExtra(CalendarContract.EXTRA_EVENT_ID, eventId); + intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, start); + intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end); + intent.putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, allDay); + intent.setFlags(flags); + try { + mContext.startActivityAsUser(intent, UserHandle.of(workProfileUserId)); + } catch (ActivityNotFoundException e) { + Log.e(LOG_TAG, "View event activity not found", e); + return false; + } + } finally { + mInjector.binderRestoreCallingIdentity(identity); + } + return true; + } + + private boolean isCallingFromPackage(String packageName, int callingUid) { + try { + final int packageUid = mInjector.getPackageManager().getPackageUidAsUser( + packageName, UserHandle.getUserId(callingUid)); + return packageUid == callingUid; + } catch (NameNotFoundException e) { + Log.d(LOG_TAG, "Calling package not found", e); + return false; + } + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java index 7910598d8429..d8a875d7747b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/UpdateInstaller.java @@ -16,6 +16,7 @@ package com.android.server.devicepolicy; +import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.app.admin.StartInstallingUpdateCallback; import android.content.Context; @@ -26,6 +27,7 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; +import android.stats.devicepolicy.DevicePolicyEnums; import android.util.Log; import java.io.File; @@ -132,6 +134,10 @@ abstract class UpdateInstaller { protected void notifyCallbackOnError(int errorCode, String errorMessage) { cleanupUpdateFile(); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.INSTALL_SYSTEM_UPDATE_ERROR) + .setInt(errorCode) + .write(); try { mCallback.onStartInstallingUpdateError(errorCode, errorMessage); } catch (RemoteException e) { diff --git a/services/intelligence/java/com/android/server/intelligence/RemoteIntelligenceService.java b/services/intelligence/java/com/android/server/intelligence/RemoteIntelligenceService.java deleted file mode 100644 index c4fbdca0ff2f..000000000000 --- a/services/intelligence/java/com/android/server/intelligence/RemoteIntelligenceService.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2018 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.intelligence; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Context; -import android.os.Bundle; -import android.os.IBinder; -import android.os.IInterface; -import android.os.RemoteException; -import android.os.SystemClock; -import android.service.intelligence.ContentCaptureEventsRequest; -import android.service.intelligence.IIntelligenceService; -import android.service.intelligence.InteractionContext; -import android.service.intelligence.InteractionSessionId; -import android.service.intelligence.SnapshotData; -import android.text.format.DateUtils; -import android.util.Slog; -import android.view.autofill.AutofillId; -import android.view.autofill.AutofillManager; -import android.view.autofill.AutofillValue; -import android.view.autofill.IAutoFillManagerClient; -import android.view.intelligence.ContentCaptureEvent; - -import com.android.internal.os.IResultReceiver; -import com.android.server.infra.AbstractMultiplePendingRequestsRemoteService; - -import java.util.List; - -//TODO(b/111276913): rename once the final name is defined -final class RemoteIntelligenceService - extends AbstractMultiplePendingRequestsRemoteService<RemoteIntelligenceService> { - - private static final String TAG = "RemoteIntelligenceService"; - - private static final long TIMEOUT_IDLE_BIND_MILLIS = 2 * DateUtils.MINUTE_IN_MILLIS; - private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS; - - private final RemoteIntelligenceServiceCallbacks mCallbacks; - private IIntelligenceService mService; - - RemoteIntelligenceService(Context context, String serviceInterface, - ComponentName componentName, int userId, - RemoteIntelligenceServiceCallbacks callbacks, boolean bindInstantServiceAllowed, - boolean verbose) { - super(context, serviceInterface, componentName, userId, callbacks, - bindInstantServiceAllowed, verbose, /* initialCapacity= */ 2); - mCallbacks = callbacks; - } - - @Override // from RemoteService - protected IInterface getServiceInterface(@NonNull IBinder service) { - mService = IIntelligenceService.Stub.asInterface(service); - return mService; - } - - // TODO(b/111276913): modify super class to allow permanent binding when value is 0 or negative - @Override // from RemoteService - protected long getTimeoutIdleBindMillis() { - // TODO(b/111276913): read from Settings so it can be changed in the field - return TIMEOUT_IDLE_BIND_MILLIS; - } - - @Override // from RemoteService - protected long getRemoteRequestMillis() { - // TODO(b/111276913): read from Settings so it can be changed in the field - return TIMEOUT_REMOTE_REQUEST_MILLIS; - } - - /** - * Called by {@link ContentCaptureSession} to generate a call to the - * {@link RemoteIntelligenceService} to indicate the session was created (when {@code context} - * is not {@code null} or destroyed (when {@code context} is {@code null}). - */ - public void onSessionLifecycleRequest(@Nullable InteractionContext context, - @NonNull InteractionSessionId sessionId) { - cancelScheduledUnbind(); - scheduleRequest(new PendingSessionLifecycleRequest(this, context, sessionId)); - } - - /** - * Called by {@link ContentCaptureSession} to send a batch of events to the service. - */ - public void onContentCaptureEventsRequest(@NonNull InteractionSessionId sessionId, - @NonNull List<ContentCaptureEvent> events) { - cancelScheduledUnbind(); - scheduleRequest(new PendingOnContentCaptureEventsRequest(this, sessionId, events)); - } - - /** - * Called by {@link ContentCaptureSession} to send snapshot data to the service. - */ - public void onActivitySnapshotRequest(@NonNull InteractionSessionId sessionId, - @NonNull SnapshotData snapshotData) { - cancelScheduledUnbind(); - scheduleRequest(new PendingOnActivitySnapshotRequest(this, sessionId, snapshotData)); - } - - /** - * Called by {@link ContentCaptureSession} to request augmented autofill. - */ - public void onRequestAutofillLocked(@NonNull InteractionSessionId sessionId, - @NonNull IAutoFillManagerClient client, int autofillSessionId, - @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue) { - cancelScheduledUnbind(); - scheduleRequest(new PendingAutofillRequest(this, sessionId, client, autofillSessionId, - focusedId, focusedValue)); - } - - /** - * Called by {@link ContentCaptureSession} when it's time to destroy all augmented autofill - * requests. - */ - public void onDestroyAutofillWindowsRequest(@NonNull InteractionSessionId sessionId) { - cancelScheduledUnbind(); - scheduleRequest(new PendingDestroyAutofillWindowsRequest(this, sessionId)); - } - - private abstract static class MyPendingRequest - extends PendingRequest<RemoteIntelligenceService> { - protected final InteractionSessionId mSessionId; - - private MyPendingRequest(@NonNull RemoteIntelligenceService service, - @NonNull InteractionSessionId sessionId) { - super(service); - mSessionId = sessionId; - } - - @Override // from PendingRequest - protected final void onTimeout(RemoteIntelligenceService remoteService) { - Slog.w(TAG, "timed out handling " + getClass().getSimpleName() + " for " - + mSessionId); - remoteService.mCallbacks.onFailureOrTimeout(/* timedOut= */ true); - } - - @Override // from PendingRequest - public final void run() { - final RemoteIntelligenceService remoteService = getService(); - if (remoteService != null) { - try { - // We don't expect the service to call us back, so we finish right away. - myRun(remoteService); - // TODO(b/111330312): not true anymore!! - finish(); - } catch (RemoteException e) { - Slog.w(TAG, "exception handling " + getClass().getSimpleName() + " for " - + mSessionId + ": " + e); - remoteService.mCallbacks.onFailureOrTimeout(/* timedOut= */ false); - } - } - } - - protected abstract void myRun(@NonNull RemoteIntelligenceService service) - throws RemoteException; - - } - - private static final class PendingSessionLifecycleRequest extends MyPendingRequest { - - private final InteractionContext mContext; - - protected PendingSessionLifecycleRequest(@NonNull RemoteIntelligenceService service, - @Nullable InteractionContext context, @NonNull InteractionSessionId sessionId) { - super(service, sessionId); - mContext = context; - } - - @Override // from MyPendingRequest - public void myRun(@NonNull RemoteIntelligenceService remoteService) throws RemoteException { - remoteService.mService.onSessionLifecycle(mContext, mSessionId); - } - } - - private static final class PendingOnContentCaptureEventsRequest extends MyPendingRequest { - - private final List<ContentCaptureEvent> mEvents; - - protected PendingOnContentCaptureEventsRequest(@NonNull RemoteIntelligenceService service, - @NonNull InteractionSessionId sessionId, - @NonNull List<ContentCaptureEvent> events) { - super(service, sessionId); - mEvents = events; - } - - @Override // from MyPendingRequest - public void myRun(@NonNull RemoteIntelligenceService remoteService) throws RemoteException { - remoteService.mService.onContentCaptureEventsRequest(mSessionId, - new ContentCaptureEventsRequest(mEvents)); - } - } - - private static final class PendingOnActivitySnapshotRequest extends MyPendingRequest { - - private final SnapshotData mSnapshotData; - - protected PendingOnActivitySnapshotRequest(@NonNull RemoteIntelligenceService service, - @NonNull InteractionSessionId sessionId, - @NonNull SnapshotData snapshotData) { - super(service, sessionId); - mSnapshotData = snapshotData; - } - - @Override // from MyPendingRequest - protected void myRun(@NonNull RemoteIntelligenceService remoteService) - throws RemoteException { - remoteService.mService.onActivitySnapshot(mSessionId, mSnapshotData); - } - } - - private static final class PendingAutofillRequest extends MyPendingRequest { - private final @NonNull AutofillId mFocusedId; - private final @Nullable AutofillValue mFocusedValue; - private final @NonNull IAutoFillManagerClient mClient; - private final int mAutofillSessionId; - private final long mRequestTime = SystemClock.elapsedRealtime(); - - protected PendingAutofillRequest(@NonNull RemoteIntelligenceService service, - @NonNull InteractionSessionId sessionId, @NonNull IAutoFillManagerClient client, - int autofillSessionId, @NonNull AutofillId focusedId, - @Nullable AutofillValue focusedValue) { - super(service, sessionId); - mClient = client; - mAutofillSessionId = autofillSessionId; - mFocusedId = focusedId; - mFocusedValue = focusedValue; - } - - @Override // from MyPendingRequest - public void myRun(@NonNull RemoteIntelligenceService remoteService) throws RemoteException { - final IResultReceiver receiver = new IResultReceiver.Stub() { - - @Override - public void send(int resultCode, Bundle resultData) throws RemoteException { - final IBinder realClient = resultData - .getBinder(AutofillManager.EXTRA_AUGMENTED_AUTOFILL_CLIENT); - remoteService.mService.onAutofillRequest(mSessionId, realClient, - mAutofillSessionId, mFocusedId, mFocusedValue, mRequestTime); - } - }; - - // TODO(b/111330312): set cancellation signal, timeout (from both mClient and service), - // cache IAugmentedAutofillManagerClient reference, etc... - mClient.getAugmentedAutofillClient(receiver); - } - } - - private static final class PendingDestroyAutofillWindowsRequest extends MyPendingRequest { - - protected PendingDestroyAutofillWindowsRequest(@NonNull RemoteIntelligenceService service, - @NonNull InteractionSessionId sessionId) { - super(service, sessionId); - } - - @Override - protected void myRun(@NonNull RemoteIntelligenceService service) throws RemoteException { - service.mService.onDestroyAutofillWindowsRequest(mSessionId); - // TODO(b/111330312): implement timeout - } - } - - public interface RemoteIntelligenceServiceCallbacks extends VultureCallback { - // To keep it simple, we use the same callback for all failures / timeouts. - void onFailureOrTimeout(boolean timedOut); - } -} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index c4d2a914facf..88f645defa6d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -36,7 +36,6 @@ import android.content.res.Configuration; import android.content.res.Resources.Theme; import android.database.sqlite.SQLiteCompatibilityWalFlags; import android.database.sqlite.SQLiteGlobal; -import android.hardware.display.ColorDisplayManager; import android.os.BaseBundle; import android.os.Binder; import android.os.Build; @@ -239,8 +238,8 @@ public final class SystemServer { "com.android.server.wallpaper.WallpaperManagerService$Lifecycle"; private static final String AUTO_FILL_MANAGER_SERVICE_CLASS = "com.android.server.autofill.AutofillManagerService"; - private static final String INTELLIGENCE_MANAGER_SERVICE_CLASS = - "com.android.server.intelligence.IntelligenceManagerService"; + private static final String CONTENT_CAPTURE_MANAGER_SERVICE_CLASS = + "com.android.server.contentcapture.ContentCaptureManagerService"; private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS = "com.android.server.timezone.RulesManagerService$Lifecycle"; private static final String IOT_SERVICE_CLASS = @@ -1136,7 +1135,7 @@ public final class SystemServer { traceEnd(); } - startIntelligenceService(context); + startContentCaptureService(context); // NOTE: ClipboardService indirectly depends on IntelligenceService traceBeginAndSlog("StartClipboardService"); @@ -1398,6 +1397,15 @@ public final class SystemServer { traceEnd(); } + // Start ADB Debugging Service + traceBeginAndSlog("StartAdbService"); + try { + mSystemServiceManager.startService(ADB_SERVICE_CLASS); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting AdbService"); + } + traceEnd(); + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST) || mPackageManager.hasSystemFeature( PackageManager.FEATURE_USB_ACCESSORY) @@ -1408,15 +1416,6 @@ public final class SystemServer { traceEnd(); } - // Start ADB Debugging Service - traceBeginAndSlog("StartAdbService"); - try { - mSystemServiceManager.startService(ADB_SERVICE_CLASS); - } catch (Throwable e) { - Slog.e(TAG, "Failure starting AdbService"); - } - traceEnd(); - if (!isWatch) { traceBeginAndSlog("StartSerialService"); try { @@ -1443,11 +1442,9 @@ public final class SystemServer { mSystemServiceManager.startService(TwilightService.class); traceEnd(); - if (ColorDisplayManager.isNightDisplayAvailable(context)) { - traceBeginAndSlog("StartColorDisplay"); - mSystemServiceManager.startService(ColorDisplayService.class); - traceEnd(); - } + traceBeginAndSlog("StartColorDisplay"); + mSystemServiceManager.startService(ColorDisplayService.class); + traceEnd(); traceBeginAndSlog("StartJobScheduler"); mSystemServiceManager.startService(JobSchedulerService.class); @@ -2100,18 +2097,18 @@ public final class SystemServer { }, BOOT_TIMINGS_TRACE_LOG); } - private void startIntelligenceService(@NonNull Context context) { + private void startContentCaptureService(@NonNull Context context) { // First check if it was explicitly enabled by Settings boolean explicitlySupported = false; final String settings = Settings.Global.getString(context.getContentResolver(), - Settings.Global.SMART_SUGGESTIONS_SERVICE_EXPLICITLY_ENABLED); + Settings.Global.CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED); if (settings != null) { explicitlySupported = Boolean.parseBoolean(settings); if (explicitlySupported) { - Slog.d(TAG, "IntelligenceService explicitly enabled by Settings"); + Slog.d(TAG, "ContentCaptureService explicitly enabled by Settings"); } else { - Slog.d(TAG, "IntelligenceService explicitly disabled by Settings"); + Slog.d(TAG, "ContentCaptureService explicitly disabled by Settings"); return; } } @@ -2119,15 +2116,15 @@ public final class SystemServer { // Then check if OEM overlaid the resource that defines the service. if (!explicitlySupported) { final String serviceName = context - .getString(com.android.internal.R.string.config_defaultSmartSuggestionsService); + .getString(com.android.internal.R.string.config_defaultContentCaptureService); if (TextUtils.isEmpty(serviceName)) { - Slog.d(TAG, "IntelligenceService disabled because config resource is not overlaid"); + Slog.d(TAG, "ContentCaptureService disabled because resource is not overlaid"); return; } } - traceBeginAndSlog("StartIntelligenceService"); - mSystemServiceManager.startService(INTELLIGENCE_MANAGER_SERVICE_CLASS); + traceBeginAndSlog("StartContentCaptureService"); + mSystemServiceManager.startService(CONTENT_CAPTURE_MANAGER_SERVICE_CLASS); traceEnd(); } diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java index ba4caf44024b..96ef0ce45001 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -22,7 +22,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.robolectric.Shadows.shadowOf; +import static org.testng.Assert.expectThrows; +import android.annotation.UserIdInt; import android.app.Application; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; @@ -32,11 +35,14 @@ import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import com.android.server.backup.testing.BackupManagerServiceTestUtils; import com.android.server.backup.testing.TransportData; +import com.android.server.testing.shadows.ShadowBinder; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,6 +50,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowContextWrapper; import java.io.File; import java.io.FileDescriptor; @@ -51,15 +59,19 @@ import java.io.PrintWriter; /** Tests for the user-aware backup/restore system service {@link BackupManagerService}. */ @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBinder.class}) @Presubmit public class BackupManagerServiceTest { private static final String TEST_PACKAGE = "package"; private static final String TEST_TRANSPORT = "transport"; + private static final int NON_USER_SYSTEM = UserHandle.USER_SYSTEM + 1; + + private ShadowContextWrapper mShadowContext; @Mock private UserBackupManagerService mUserBackupManagerService; - @Mock private TransportManager mTransportManager; private BackupManagerService mBackupManagerService; private Context mContext; + @UserIdInt private int mUserId; /** Initialize {@link BackupManagerService}. */ @Before @@ -68,18 +80,26 @@ public class BackupManagerServiceTest { Application application = RuntimeEnvironment.application; mContext = application; + mShadowContext = shadowOf(application); + mUserId = NON_USER_SYSTEM; mBackupManagerService = new BackupManagerService( application, new Trampoline(application), - BackupManagerServiceTestUtils.startBackupThread(null), - new File(application.getCacheDir(), "base_state"), - new File(application.getCacheDir(), "data"), - mTransportManager); + BackupManagerServiceTestUtils.startBackupThread(null)); mBackupManagerService.setUserBackupManagerService(mUserBackupManagerService); } /** + * Clean up and reset state that was created for testing {@link BackupManagerService} + * operations. + */ + @After + public void tearDown() throws Exception { + ShadowBinder.reset(); + } + + /** * Test verifying that {@link BackupManagerService#MORE_DEBUG} is set to {@code false}. * This is specifically to prevent overloading the logs in production. */ @@ -278,11 +298,41 @@ public class BackupManagerServiceTest { // --------------------------------------------- // Settings tests // --------------------------------------------- + /** + * Test verifying that {@link BackupManagerService#setBackupEnabled(int, boolean)} throws a + * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission. + */ + @Test + public void setBackupEnabled_withoutPermission_throwsSecurityException() { + mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + + expectThrows( + SecurityException.class, + () -> mBackupManagerService.setBackupEnabled(mUserId, true)); + } + + /** + * Test verifying that {@link BackupManagerService#setBackupEnabled(int, boolean)} does not + * require the caller to have INTERACT_ACROSS_USERS_FULL permission when the calling user id is + * the same as the target user id. + */ + @Test + public void setBackupEnabled_whenCallingUserIsTargetUser_doesntNeedPermission() { + ShadowBinder.setCallingUserHandle(UserHandle.of(mUserId)); + mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + + mBackupManagerService.setBackupEnabled(mUserId, true); + + verify(mUserBackupManagerService).setBackupEnabled(true); + } + /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void setBackupEnabled_callsSetBackupEnabledForUser() throws Exception { - mBackupManagerService.setBackupEnabled(true); + mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + + mBackupManagerService.setBackupEnabled(mUserId, true); verify(mUserBackupManagerService).setBackupEnabled(true); } @@ -303,10 +353,25 @@ public class BackupManagerServiceTest { verify(mUserBackupManagerService).setBackupProvisioned(true); } + /** + * Test verifying that {@link BackupManagerService#isBackupEnabled(int)} throws a + * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission. + */ + @Test + public void testIsBackupEnabled_withoutPermission_throwsSecurityException() { + mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + + expectThrows( + SecurityException.class, + () -> mBackupManagerService.isBackupEnabled(mUserId)); + } + /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testIsBackupEnabled_callsIsBackupEnabledForUser() throws Exception { - mBackupManagerService.isBackupEnabled(); + mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + + mBackupManagerService.isBackupEnabled(mUserId); verify(mUserBackupManagerService).isBackupEnabled(); } @@ -334,30 +399,81 @@ public class BackupManagerServiceTest { verify(mUserBackupManagerService).filterAppsEligibleForBackup(packages); } + /** + * Test verifying that {@link BackupManagerService#backupNow(int)} throws a + * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission. + */ + @Test + public void testBackupNow_withoutPermission_throwsSecurityException() { + mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + + expectThrows( + SecurityException.class, + () -> mBackupManagerService.backupNow(mUserId)); + } + /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testBackupNow_callsBackupNowForUser() throws Exception { - mBackupManagerService.backupNow(); + mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + + mBackupManagerService.backupNow(mUserId); verify(mUserBackupManagerService).backupNow(); } + /** + * Test verifying that {@link BackupManagerService#requestBackup(int, String[], IBackupObserver, + * IBackupManagerMonitor, int)} throws a {@link SecurityException} if the caller does not have + * INTERACT_ACROSS_USERS_FULL permission. + */ + @Test + public void testRequestBackup_withoutPermission_throwsSecurityException() { + mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + String[] packages = {TEST_PACKAGE}; + IBackupObserver observer = mock(IBackupObserver.class); + IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); + + expectThrows( + SecurityException.class, + () -> mBackupManagerService.requestBackup(mUserId, packages, observer, monitor, 0)); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testRequestBackup_callsRequestBackupForUser() throws Exception { + mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); String[] packages = {TEST_PACKAGE}; IBackupObserver observer = mock(IBackupObserver.class); IBackupManagerMonitor monitor = mock(IBackupManagerMonitor.class); - mBackupManagerService.requestBackup(packages, observer, monitor, /* flags */ 0); + mBackupManagerService.requestBackup(mUserId, packages, observer, monitor, + /* flags */ 0); verify(mUserBackupManagerService).requestBackup(packages, observer, monitor, /* flags */ 0); } + /** + * Test verifying that {@link BackupManagerService#cancelBackups(int)} throws a + * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission. + */ + @Test + public void testCancelBackups_withoutPermission_throwsSecurityException() { + mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + + expectThrows( + SecurityException.class, + () -> mBackupManagerService.cancelBackups(mUserId)); + } + + /** Test that the backup service routes methods correctly to the user that requests it. */ @Test public void testCancelBackups_callsCancelBackupsForUser() throws Exception { - mBackupManagerService.cancelBackups(); + mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + + mBackupManagerService.cancelBackups(mUserId); verify(mUserBackupManagerService).cancelBackups(); } diff --git a/services/robotests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/UserBackupManagerServiceTest.java index 9d4381914608..efbcb960c1e9 100644 --- a/services/robotests/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/robotests/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -148,7 +148,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenReturn("destinationString"); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); String destination = backupManagerService.getDestinationString(mTransportName); @@ -164,7 +164,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenThrow(TransportNotRegisteredException.class); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); String destination = backupManagerService.getDestinationString(mTransportName); @@ -180,7 +180,7 @@ public class UserBackupManagerServiceTest { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenThrow(TransportNotRegisteredException.class); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( SecurityException.class, @@ -195,7 +195,7 @@ public class UserBackupManagerServiceTest { public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); setUpCurrentTransport(mTransportManager, mTransport); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); boolean result = backupManagerService.isAppEligibleForBackup(PACKAGE_1); @@ -211,7 +211,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); TransportMock transportMock = setUpCurrentTransport(mTransportManager, backupTransport()); ShadowAppBackupUtils.setAppRunningAndEligibleForBackupWithTransport(PACKAGE_1); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); boolean result = backupManagerService.isAppEligibleForBackup(PACKAGE_1); @@ -229,7 +229,7 @@ public class UserBackupManagerServiceTest { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); setUpCurrentTransport(mTransportManager, mTransport); ShadowAppBackupUtils.setAppRunningAndEligibleForBackupWithTransport(PACKAGE_1); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( SecurityException.class, @@ -246,7 +246,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); TransportMock transportMock = setUpCurrentTransport(mTransportManager, mTransport); ShadowAppBackupUtils.setAppRunningAndEligibleForBackupWithTransport(PACKAGE_1); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); String[] filtered = backupManagerService.filterAppsEligibleForBackup( @@ -264,7 +264,7 @@ public class UserBackupManagerServiceTest { @Test public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); String[] filtered = backupManagerService.filterAppsEligibleForBackup( @@ -281,7 +281,7 @@ public class UserBackupManagerServiceTest { public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); setUpCurrentTransport(mTransportManager, mTransport); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( SecurityException.class, @@ -319,7 +319,7 @@ public class UserBackupManagerServiceTest { public void testSelectBackupTransport() throws Exception { setUpForSelectTransport(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); String oldTransport = backupManagerService.selectBackupTransport(mNewTransport.transportName); @@ -338,7 +338,7 @@ public class UserBackupManagerServiceTest { public void testSelectBackupTransport_withoutPermission() throws Exception { setUpForSelectTransport(); mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( SecurityException.class, @@ -356,7 +356,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) .thenReturn(BackupManager.SUCCESS); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); @@ -380,7 +380,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) .thenReturn(BackupManager.ERROR_TRANSPORT_UNAVAILABLE); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); @@ -402,7 +402,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(newTransportComponent))) .thenReturn(BackupManager.SUCCESS); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); backupManagerService.selectBackupTransportAsync(newTransportComponent, callback); @@ -421,7 +421,7 @@ public class UserBackupManagerServiceTest { public void testSelectBackupTransportAsync_withoutPermission() throws Exception { setUpForSelectTransport(); mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); ComponentName newTransportComponent = mNewTransport.getTransportComponent(); expectThrows( @@ -445,7 +445,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.getCurrentTransportComponent()) .thenReturn(mTransport.getTransportComponent()); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); ComponentName transportComponent = backupManagerService.getCurrentTransportComponent(); @@ -460,7 +460,7 @@ public class UserBackupManagerServiceTest { public void testGetCurrentTransportComponent_whenNoTransportSelected() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.getCurrentTransportComponent()).thenReturn(null); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); ComponentName transportComponent = backupManagerService.getCurrentTransportComponent(); @@ -476,7 +476,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.getCurrentTransportComponent()) .thenThrow(TransportNotRegisteredException.class); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); ComponentName transportComponent = backupManagerService.getCurrentTransportComponent(); @@ -490,7 +490,7 @@ public class UserBackupManagerServiceTest { @Test public void testGetCurrentTransportComponent_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows(SecurityException.class, backupManagerService::getCurrentTransportComponent); } @@ -525,7 +525,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); Intent configurationIntent = new Intent(); Intent dataManagementIntent = new Intent(); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); backupManagerService.updateTransportAttributes( mTransportUid, @@ -556,7 +556,7 @@ public class UserBackupManagerServiceTest { throws Exception { setUpForUpdateTransportAttributes(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( SecurityException.class, @@ -581,7 +581,7 @@ public class UserBackupManagerServiceTest { throws Exception { setUpForUpdateTransportAttributes(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( RuntimeException.class, @@ -605,7 +605,7 @@ public class UserBackupManagerServiceTest { public void testUpdateTransportAttributes_whenNameNull_throwsException() throws Exception { setUpForUpdateTransportAttributes(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( RuntimeException.class, @@ -630,7 +630,7 @@ public class UserBackupManagerServiceTest { throws Exception { setUpForUpdateTransportAttributes(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( RuntimeException.class, @@ -657,7 +657,7 @@ public class UserBackupManagerServiceTest { throws Exception { setUpForUpdateTransportAttributes(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( RuntimeException.class, @@ -696,7 +696,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); Intent configurationIntent = new Intent(); Intent dataManagementIntent = new Intent(); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); backupManagerService.updateTransportAttributes( mTransportUid, @@ -727,7 +727,7 @@ public class UserBackupManagerServiceTest { throws Exception { setUpForUpdateTransportAttributes(); mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( SecurityException.class, @@ -766,7 +766,7 @@ public class UserBackupManagerServiceTest { @Test public void testRequestBackup_whenPermissionDenied() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( SecurityException.class, @@ -780,7 +780,7 @@ public class UserBackupManagerServiceTest { @Test public void testRequestBackup_whenPackagesNull() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( IllegalArgumentException.class, @@ -796,7 +796,7 @@ public class UserBackupManagerServiceTest { @Test public void testRequestBackup_whenPackagesEmpty() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); expectThrows( IllegalArgumentException.class, @@ -811,7 +811,7 @@ public class UserBackupManagerServiceTest { @Test public void testRequestBackup_whenBackupDisabled() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); backupManagerService.setEnabled(false); int result = backupManagerService.requestBackup(new String[] {PACKAGE_1}, mObserver, 0); @@ -828,7 +828,7 @@ public class UserBackupManagerServiceTest { @Test public void testRequestBackup_whenNotProvisioned() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); backupManagerService.setProvisioned(false); int result = backupManagerService.requestBackup(new String[] {PACKAGE_1}, mObserver, 0); @@ -846,7 +846,7 @@ public class UserBackupManagerServiceTest { public void testRequestBackup_whenTransportNotRegistered() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); setUpCurrentTransport(mTransportManager, mTransport.unregistered()); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); backupManagerService.setEnabled(true); backupManagerService.setProvisioned(true); @@ -866,7 +866,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); mShadowPackageManager.addPackage(PACKAGE_1); setUpCurrentTransport(mTransportManager, mTransport); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); backupManagerService.setEnabled(true); backupManagerService.setProvisioned(true); // Haven't set PACKAGE_1 as eligible @@ -934,7 +934,7 @@ public class UserBackupManagerServiceTest { @Config(shadows = {ShadowBinder.class, ShadowKeyValueBackupJob.class}) public void testBackupNow_clearsCallingIdentityForJobScheduler() { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); setUpPowerManager(backupManagerService); ShadowBinder.setCallingUid(1); @@ -952,7 +952,7 @@ public class UserBackupManagerServiceTest { @Config(shadows = {ShadowBinder.class, ShadowKeyValueBackupJobException.class}) public void testBackupNow_whenExceptionThrown_restoresCallingIdentity() { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); setUpPowerManager(backupManagerService); ShadowBinder.setCallingUid(1); @@ -963,54 +963,170 @@ public class UserBackupManagerServiceTest { } private UserBackupManagerService createBackupManagerServiceForRequestBackup() { - UserBackupManagerService backupManagerService = createInitializedBackupManagerService(); + UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); backupManagerService.setEnabled(true); backupManagerService.setProvisioned(true); return backupManagerService; } /** - * Test verifying that {@link UserBackupManagerService#UserBackupManagerService(Context, + * Test verifying that {@link UserBackupManagerService#createAndInitializeService(Context, * Trampoline, HandlerThread, File, File, TransportManager)} posts a transport registration task - * to the backup handler thread. + * to the backup thread. */ @Test - public void testConstructor_postRegisterTransports() { + public void testCreateAndInitializeService_postRegisterTransports() { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - createBackupManagerService(); + UserBackupManagerService.createAndInitializeService( + mContext, + new Trampoline(mContext), + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager); mShadowBackupLooper.runToEndOfTasks(); verify(mTransportManager).registerTransports(); } /** - * Test verifying that the {@link UserBackupManagerService#UserBackupManagerService(Context, + * Test verifying that {@link UserBackupManagerService#createAndInitializeService(Context, * Trampoline, HandlerThread, File, File, TransportManager)} does not directly register - * transports in its own thread. + * transports on the main thread. */ @Test - public void testConstructor_doesNotRegisterTransportsSynchronously() { + public void testCreateAndInitializeService_doesNotRegisterTransportsSynchronously() { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - createBackupManagerService(); - - // Operations posted to mBackupThread only run with mShadowBackupLooper.runToEndOfTasks() - verify(mTransportManager, never()).registerTransports(); - } - - private UserBackupManagerService createBackupManagerService() { - return new UserBackupManagerService( + UserBackupManagerService.createAndInitializeService( mContext, new Trampoline(mContext), mBackupThread, mBaseStateDir, mDataDir, mTransportManager); + + // Operations posted to mBackupThread only run with mShadowBackupLooper.runToEndOfTasks() + verify(mTransportManager, never()).registerTransports(); + } + + /** + * Test checking non-null argument on {@link + * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File, + * File, TransportManager)}. + */ + @Test + public void testCreateAndInitializeService_withNullContext_throws() { + expectThrows( + NullPointerException.class, + () -> + UserBackupManagerService.createAndInitializeService( + /* context */ null, + new Trampoline(mContext), + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager)); + } + + /** + * Test checking non-null argument on {@link + * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File, + * File, TransportManager)}. + */ + @Test + public void testCreateAndInitializeService_withNullTrampoline_throws() { + expectThrows( + NullPointerException.class, + () -> + UserBackupManagerService.createAndInitializeService( + mContext, + /* trampoline */ null, + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager)); + } + + /** + * Test checking non-null argument on {@link + * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File, + * File, TransportManager)}. + */ + @Test + public void testCreateAndInitializeService_withNullBackupThread_throws() { + expectThrows( + NullPointerException.class, + () -> + UserBackupManagerService.createAndInitializeService( + mContext, + new Trampoline(mContext), + /* backupThread */ null, + mBaseStateDir, + mDataDir, + mTransportManager)); + } + + /** + * Test checking non-null argument on {@link + * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File, + * File, TransportManager)}. + */ + @Test + public void testCreateAndInitializeService_withNullStateDir_throws() { + expectThrows( + NullPointerException.class, + () -> + UserBackupManagerService.createAndInitializeService( + mContext, + new Trampoline(mContext), + mBackupThread, + /* baseStateDir */ null, + mDataDir, + mTransportManager)); + } + + /** + * Test checking non-null argument on {@link + * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File, + * File, TransportManager)}. + */ + @Test + public void testCreateAndInitializeService_withNullDataDir_throws() { + expectThrows( + NullPointerException.class, + () -> + UserBackupManagerService.createAndInitializeService( + mContext, + new Trampoline(mContext), + mBackupThread, + mBaseStateDir, + /* dataDir */ null, + mTransportManager)); + } + + /** + * Test checking non-null argument on {@link + * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File, + * File, TransportManager)}. + */ + @Test + public void testCreateAndInitializeService_withNullTransportManager_throws() { + expectThrows( + NullPointerException.class, + () -> + UserBackupManagerService.createAndInitializeService( + mContext, + new Trampoline(mContext), + mBackupThread, + mBaseStateDir, + mDataDir, + /* transportManager */ null)); } - private UserBackupManagerService createInitializedBackupManagerService() { - return BackupManagerServiceTestUtils.createInitializedUserBackupManagerService( + private UserBackupManagerService createUserBackupManagerServiceAndRunTasks() { + return BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks( mContext, mBackupThread, mBaseStateDir, mDataDir, mTransportManager); } diff --git a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index 1aa4999b1d3a..099127cbeb4b 100644 --- a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -25,12 +25,9 @@ import static android.app.backup.BackupManager.SUCCESS; import static android.app.backup.ForwardingBackupAgent.forward; import static com.android.server.backup.testing.BackupManagerServiceTestUtils.createBackupWakeLock; -import static com.android.server.backup.testing.BackupManagerServiceTestUtils - .createInitializedUserBackupManagerService; -import static com.android.server.backup.testing.BackupManagerServiceTestUtils - .setUpBackupManagerServiceBasics; -import static com.android.server.backup.testing.BackupManagerServiceTestUtils - .setUpBinderCallerAndApplicationAsSystem; +import static com.android.server.backup.testing.BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks; +import static com.android.server.backup.testing.BackupManagerServiceTestUtils.setUpBackupManagerServiceBasics; +import static com.android.server.backup.testing.BackupManagerServiceTestUtils.setUpBinderCallerAndApplicationAsSystem; import static com.android.server.backup.testing.PackageData.PM_PACKAGE; import static com.android.server.backup.testing.PackageData.fullBackupPackage; import static com.android.server.backup.testing.PackageData.keyValuePackage; @@ -226,9 +223,8 @@ public class KeyValueBackupTaskTest { // Needed to be able to use a real BMS instead of a mock setUpBinderCallerAndApplicationAsSystem(mApplication); mBackupManagerService = - spy( - createInitializedUserBackupManagerService( - mContext, mBaseStateDir, mDataDir, mTransportManager)); + spy(createUserBackupManagerServiceAndRunTasks( + mContext, mBaseStateDir, mDataDir, mTransportManager)); setUpBackupManagerServiceBasics( mBackupManagerService, mApplication, diff --git a/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java b/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java index bacc44e685f4..06f6d21b9ca9 100644 --- a/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java +++ b/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java @@ -52,23 +52,36 @@ import java.util.concurrent.atomic.AtomicReference; /** Test utils for {@link UserBackupManagerService} and friends. */ public class BackupManagerServiceTestUtils { /** - * If the class-under-test is going to execute methods as the system, it's a good idea to also - * call {@link #setUpBinderCallerAndApplicationAsSystem(Application)} before this method. + * Creates an instance of {@link UserBackupManagerService} with a new backup thread and runs + * tasks that were posted to it during instantiation. + * + * <p>If the class-under-test is going to execute methods as the system, it's a good idea to + * also call {@link #setUpBinderCallerAndApplicationAsSystem(Application)} before this method. + * + * @see #createUserBackupManagerServiceAndRunTasks(Context, HandlerThread, File, File, + * TransportManager) */ - public static UserBackupManagerService createInitializedUserBackupManagerService( + public static UserBackupManagerService createUserBackupManagerServiceAndRunTasks( Context context, File baseStateDir, File dataDir, TransportManager transportManager) { - return createInitializedUserBackupManagerService( + return createUserBackupManagerServiceAndRunTasks( context, startBackupThread(null), baseStateDir, dataDir, transportManager); } - public static UserBackupManagerService createInitializedUserBackupManagerService( + /** + * Creates an instance of {@link UserBackupManagerService} with the supplied backup thread + * {@code backupThread} and runs tasks that were posted to it during instantiation. + * + * <p>If the class-under-test is going to execute methods as the system, it's a good idea to + * also call {@link #setUpBinderCallerAndApplicationAsSystem(Application)} before this method. + */ + public static UserBackupManagerService createUserBackupManagerServiceAndRunTasks( Context context, HandlerThread backupThread, File baseStateDir, File dataDir, TransportManager transportManager) { UserBackupManagerService backupManagerService = - new UserBackupManagerService( + UserBackupManagerService.createAndInitializeService( context, new Trampoline(context), backupThread, diff --git a/services/robotests/src/com/android/server/location/GnssGeofenceProviderTest.java b/services/robotests/src/com/android/server/location/GnssGeofenceProviderTest.java index beb59414546a..30c73368da15 100644 --- a/services/robotests/src/com/android/server/location/GnssGeofenceProviderTest.java +++ b/services/robotests/src/com/android/server/location/GnssGeofenceProviderTest.java @@ -7,7 +7,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.os.Looper; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; @@ -44,7 +43,7 @@ public class GnssGeofenceProviderTest { when(mMockNative.pauseGeofence(anyInt())).thenReturn(true); when(mMockNative.removeGeofence(anyInt())).thenReturn(true); when(mMockNative.resumeGeofence(anyInt(), anyInt())).thenReturn(true); - mTestProvider = new GnssGeofenceProvider(Looper.myLooper(), mMockNative); + mTestProvider = new GnssGeofenceProvider(mMockNative); mTestProvider.addCircularHardwareGeofence(GEOFENCE_ID, LATITUDE, LONGITUDE, RADIUS, LAST_TRANSITION, MONITOR_TRANSITIONS, NOTIFICATION_RESPONSIVENESS, diff --git a/services/robotests/src/com/android/server/location/GnssNavigationMessageProviderTest.java b/services/robotests/src/com/android/server/location/GnssNavigationMessageProviderTest.java index 59e9a15efb53..aa2a96e6fad4 100644 --- a/services/robotests/src/com/android/server/location/GnssNavigationMessageProviderTest.java +++ b/services/robotests/src/com/android/server/location/GnssNavigationMessageProviderTest.java @@ -16,6 +16,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; /** * Unit tests for {@link GnssNavigationMessageProvider}. @@ -33,8 +34,8 @@ public class GnssNavigationMessageProviderTest { when(mMockNative.startNavigationMessageCollection()).thenReturn(true); when(mMockNative.stopNavigationMessageCollection()).thenReturn(true); - mTestProvider = new GnssNavigationMessageProvider(new Handler(Looper.myLooper()), - mMockNative) { + mTestProvider = new GnssNavigationMessageProvider(RuntimeEnvironment.application, + new Handler(Looper.myLooper()), mMockNative) { @Override public boolean isGpsEnabled() { return true; diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java index 1ece49ea2060..9de9f502d843 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java @@ -17,9 +17,11 @@ package com.android.server.testing.shadows; import android.os.Binder; +import android.os.UserHandle; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; /** * Extends {@link org.robolectric.shadows.ShadowBinder} with {@link Binder#clearCallingIdentity()} @@ -30,6 +32,7 @@ import org.robolectric.annotation.Implements; public class ShadowBinder extends org.robolectric.shadows.ShadowBinder { public static final Integer LOCAL_UID = 1000; private static Integer originalCallingUid; + private static UserHandle sCallingUserHandle; @Implementation protected static long clearCallingIdentity() { @@ -42,4 +45,30 @@ public class ShadowBinder extends org.robolectric.shadows.ShadowBinder { protected static void restoreCallingIdentity(long token) { setCallingUid(originalCallingUid); } + + public static void setCallingUserHandle(UserHandle userHandle) { + sCallingUserHandle = userHandle; + } + + /** + * Shadows {@link Binder#getCallingUserHandle()}. If {@link ShadowBinder#sCallingUserHandle} + * is set, return that; otherwise mimic the default implementation. + */ + @Implementation + public static UserHandle getCallingUserHandle() { + if (sCallingUserHandle != null) { + return sCallingUserHandle; + } else { + return UserHandle.of(UserHandle.getUserId(getCallingUid())); + } + } + + /** + * Clean up and reset state that was created for testing. + */ + @Resetter + public static void reset() { + sCallingUserHandle = null; + org.robolectric.shadows.ShadowBinder.reset(); + } } diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index e80434224633..d7b1cb475bb4 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -69,10 +69,10 @@ android_test { "liblog", "liblzma", "libnativehelper", - "libnetdaidl", "libui", "libunwind", "libutils", + "netd_aidl_interface-cpp", ], dxflags: ["--multi-dex"], @@ -88,7 +88,7 @@ java_library { "utils/**/*.java", ], static_libs: [ - "android-support-test", + "junit", "mockito-target-minus-junit4", ], libs: [ diff --git a/services/tests/servicestests/src/com/android/server/BinderCallsStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/BinderCallsStatsServiceTest.java index f70efdfadfd7..f75617ec5200 100644 --- a/services/tests/servicestests/src/com/android/server/BinderCallsStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/BinderCallsStatsServiceTest.java @@ -18,7 +18,6 @@ package com.android.server; import static org.junit.Assert.assertEquals; -import android.os.Binder; import android.os.Process; import android.platform.test.annotations.Presubmit; @@ -26,9 +25,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.os.BinderInternal; -import com.android.internal.os.BinderInternal.CallSession; -import com.android.server.BinderCallsStatsService.WorkSourceProvider; +import com.android.server.BinderCallsStatsService.AuthorizedWorkSourceProvider; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,7 +36,7 @@ import org.junit.runner.RunWith; public class BinderCallsStatsServiceTest { @Test public void weTrustOurselves() { - WorkSourceProvider workSourceProvider = new WorkSourceProvider() { + AuthorizedWorkSourceProvider workSourceProvider = new AuthorizedWorkSourceProvider() { protected int getCallingUid() { return Process.myUid(); } @@ -55,7 +52,7 @@ public class BinderCallsStatsServiceTest { @Test public void workSourceSetIfCallerHasPermission() { - WorkSourceProvider workSourceProvider = new WorkSourceProvider() { + AuthorizedWorkSourceProvider workSourceProvider = new AuthorizedWorkSourceProvider() { protected int getCallingUid() { // System process uid which as UPDATE_DEVICE_STATS. return 1001; @@ -72,7 +69,7 @@ public class BinderCallsStatsServiceTest { @Test public void workSourceResolvedToCallingUid() { - WorkSourceProvider workSourceProvider = new WorkSourceProvider() { + AuthorizedWorkSourceProvider workSourceProvider = new AuthorizedWorkSourceProvider() { protected int getCallingUid() { // UID without permissions. return Integer.MAX_VALUE; @@ -86,40 +83,4 @@ public class BinderCallsStatsServiceTest { assertEquals(Integer.MAX_VALUE, workSourceProvider.resolveWorkSourceUid()); } - - @Test - public void workSourceSet() { - TestObserver observer = new TestObserver(); - observer.callStarted(new Binder(), 0); - assertEquals(true, observer.workSourceSet); - } - - static class TestObserver extends BinderCallsStatsService.BinderCallsObserver { - public boolean workSourceSet = false; - - TestObserver() { - super(new NoopObserver(), new WorkSourceProvider()); - } - - @Override - protected void setThreadLocalWorkSourceUid(int uid) { - workSourceSet = true; - } - } - - - static class NoopObserver implements BinderInternal.Observer { - @Override - public CallSession callStarted(Binder binder, int code) { - return null; - } - - @Override - public void callEnded(CallSession s, int parcelRequestSize, int parcelReplySize) { - } - - @Override - public void callThrewException(CallSession s, Exception exception) { - } - } } diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java index 7c002995a769..751ed9b1bd15 100644 --- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java @@ -23,11 +23,13 @@ import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.annotation.UserIdInt; import android.app.backup.BackupManager; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; @@ -43,10 +45,14 @@ import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.provider.Settings; +import android.test.mock.MockContentResolver; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.test.FakeSettingsProvider; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -81,8 +87,9 @@ public class TrampolineTest { new ComponentName("package1", "class1"), new ComponentName("package2", "class2") }; - private final int NON_USER_SYSTEM = UserHandle.USER_SYSTEM + 1; + private static final int NON_USER_SYSTEM = UserHandle.USER_SYSTEM + 1; + @UserIdInt private int mUserId; @Mock private BackupManagerService mBackupManagerServiceMock; @Mock private Context mContextMock; @Mock private File mSuppressFileMock; @@ -97,6 +104,7 @@ public class TrampolineTest { private FileDescriptor mFileDescriptorStub = new FileDescriptor(); private TrampolineTestable mTrampoline; + private MockContentResolver mContentResolver; @Before public void setUp() { @@ -104,12 +112,18 @@ public class TrampolineTest { TrampolineTestable.sBackupManagerServiceMock = mBackupManagerServiceMock; TrampolineTestable.sSuppressFile = mSuppressFileMock; + TrampolineTestable.sCallingUserId = UserHandle.USER_SYSTEM; TrampolineTestable.sCallingUid = Process.SYSTEM_UID; TrampolineTestable.sBackupDisabled = false; when(mSuppressFileMock.getParentFile()).thenReturn(mSuppressFileParentMock); + mUserId = NON_USER_SYSTEM; mTrampoline = new TrampolineTestable(mContextMock); + + mContentResolver = new MockContentResolver(); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(mContextMock.getContentResolver()).thenReturn(mContentResolver); } @Test @@ -118,6 +132,24 @@ public class TrampolineTest { } @Test + public void startServiceForUser_whenMultiUserSettingDisabled_isIgnored() { + Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); + + mTrampoline.startServiceForUser(10); + + verify(mBackupManagerServiceMock, never()).startServiceForUser(10); + } + + @Test + public void startServiceForUser_whenMultiUserSettingEnabled_callsBackupManagerService() { + Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1); + + mTrampoline.startServiceForUser(10); + + verify(mBackupManagerServiceMock).startServiceForUser(10); + } + + @Test public void initializeService_forUserSystem_successfullyInitialized() { mTrampoline.initializeService(UserHandle.USER_SYSTEM); @@ -333,10 +365,22 @@ public class TrampolineTest { } @Test - public void setBackupEnabled_forwarded() throws RemoteException { + public void setBackupEnabledForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.setBackupEnabledForUser(mUserId, true); + + verify(mBackupManagerServiceMock).setBackupEnabled(mUserId, true); + } + + @Test + public void setBackupEnabled_forwardedToCallingUserId() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.setBackupEnabled(true); - verify(mBackupManagerServiceMock).setBackupEnabled(true); + + verify(mBackupManagerServiceMock).setBackupEnabled(mUserId, true); } @Test @@ -372,10 +416,22 @@ public class TrampolineTest { } @Test - public void isBackupEnabled_forwarded() throws RemoteException { + public void isBackupEnabledForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.isBackupEnabledForUser(mUserId); + + verify(mBackupManagerServiceMock).isBackupEnabled(mUserId); + } + + @Test + public void isBackupEnabled_forwardedToCallingUserId() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.isBackupEnabled(); - verify(mBackupManagerServiceMock).isBackupEnabled(); + + verify(mBackupManagerServiceMock).isBackupEnabled(mUserId); } @Test @@ -411,10 +467,22 @@ public class TrampolineTest { } @Test - public void backupNow_forwarded() throws RemoteException { + public void backupNowForUser_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.backupNowForUser(mUserId); + + verify(mBackupManagerServiceMock).backupNow(mUserId); + } + + @Test + public void backupNow_forwardedToCallingUserId() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.backupNow(); - verify(mBackupManagerServiceMock).backupNow(); + + verify(mBackupManagerServiceMock).backupNow(mUserId); } @Test @@ -770,15 +838,28 @@ public class TrampolineTest { } @Test - public void requestBackup_forwarded() throws RemoteException { - when(mBackupManagerServiceMock.requestBackup(PACKAGE_NAMES, mBackupObserverMock, - mBackupManagerMonitorMock, 123)).thenReturn(456); + public void requestBackupForUser_forwarded() throws RemoteException { + when(mBackupManagerServiceMock.requestBackup(mUserId, PACKAGE_NAMES, + mBackupObserverMock, mBackupManagerMonitorMock, 123)).thenReturn(456); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + assertEquals(456, mTrampoline.requestBackupForUser(mUserId, PACKAGE_NAMES, + mBackupObserverMock, mBackupManagerMonitorMock, 123)); + verify(mBackupManagerServiceMock).requestBackup(mUserId, PACKAGE_NAMES, + mBackupObserverMock, mBackupManagerMonitorMock, 123); + } + @Test + public void requestBackup_forwardedToCallingUserId() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; + when(mBackupManagerServiceMock.requestBackup(NON_USER_SYSTEM, PACKAGE_NAMES, + mBackupObserverMock, mBackupManagerMonitorMock, 123)).thenReturn(456); mTrampoline.initializeService(UserHandle.USER_SYSTEM); - assertEquals(456, mTrampoline.requestBackup(PACKAGE_NAMES, mBackupObserverMock, - mBackupManagerMonitorMock, 123)); - verify(mBackupManagerServiceMock).requestBackup(PACKAGE_NAMES, mBackupObserverMock, - mBackupManagerMonitorMock, 123); + + assertEquals(456, mTrampoline.requestBackup(PACKAGE_NAMES, + mBackupObserverMock, mBackupManagerMonitorMock, 123)); + verify(mBackupManagerServiceMock).requestBackup(mUserId, PACKAGE_NAMES, + mBackupObserverMock, mBackupManagerMonitorMock, 123); } @Test @@ -788,10 +869,22 @@ public class TrampolineTest { } @Test - public void cancelBackups_forwarded() throws RemoteException { + public void cancelBackupsForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.cancelBackupsForUser(mUserId); + + verify(mBackupManagerServiceMock).cancelBackups(mUserId); + } + + @Test + public void cancelBackups_forwardedToCallingUserId() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.cancelBackups(); - verify(mBackupManagerServiceMock).cancelBackups(); + + verify(mBackupManagerServiceMock).cancelBackups(mUserId); } @Test @@ -863,6 +956,7 @@ public class TrampolineTest { private static class TrampolineTestable extends Trampoline { static boolean sBackupDisabled = false; static File sSuppressFile = null; + static int sCallingUserId = -1; static int sCallingUid = -1; static BackupManagerService sBackupManagerServiceMock = null; private int mCreateServiceCallsCount = 0; @@ -881,6 +975,10 @@ public class TrampolineTest { return sSuppressFile; } + protected int binderGetCallingUserId() { + return sCallingUserId; + } + @Override protected int binderGetCallingUid() { return sCallingUid; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 5dc6d8373f27..729fac5b1dff 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -21,6 +21,9 @@ import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID; import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; +import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY; import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY; @@ -48,6 +51,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.argThat; +import static org.testng.Assert.assertThrows; import android.Manifest.permission; import android.annotation.RawRes; @@ -4163,7 +4167,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // test reset password with token when(getServices().lockPatternUtils.setLockCredentialWithToken(eq(password), eq(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), - eq(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED), eq(handle), eq(token), + eq(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), eq(handle), eq(token), eq(UserHandle.USER_SYSTEM))) .thenReturn(true); assertTrue(dpm.resetPasswordWithToken(admin1, password, token, 0)); @@ -5133,6 +5137,71 @@ public class DevicePolicyManagerTest extends DpmTestBase { }); } + public void testGetPasswordComplexity_securityExceptionIfParentInstance() { + assertThrows(SecurityException.class, + () -> new DevicePolicyManagerTestable( + mServiceContext, + dpms, + /* parentInstance= */ true) + .getPasswordComplexity()); + } + + public void testGetPasswordComplexity_illegalStateExceptionIfLocked() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(false); + assertThrows(IllegalStateException.class, () -> dpm.getPasswordComplexity()); + } + + public void testGetPasswordComplexity_securityExceptionWithoutPermissions() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(true); + assertThrows(SecurityException.class, () -> dpm.getPasswordComplexity()); + } + + + public void testGetPasswordComplexity_currentUserNoPassword() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(true); + mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY); + when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(DpmMockContext.CALLER_USER_HANDLE); + + assertEquals(PASSWORD_COMPLEXITY_NONE, dpm.getPasswordComplexity()); + } + + public void testGetPasswordComplexity_currentUserHasPassword() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(true); + mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY); + when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(DpmMockContext.CALLER_USER_HANDLE); + dpms.mUserPasswordMetrics.put( + DpmMockContext.CALLER_USER_HANDLE, + PasswordMetrics.computeForPassword("asdf")); + + assertEquals(PASSWORD_COMPLEXITY_MEDIUM, dpm.getPasswordComplexity()); + } + + public void testGetPasswordComplexity_unifiedChallengeReturnsParentUserPassword() { + when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(true); + mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY); + + UserInfo parentUser = new UserInfo(); + parentUser.id = DpmMockContext.CALLER_USER_HANDLE + 10; + when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE)) + .thenReturn(parentUser.id); + + dpms.mUserPasswordMetrics.put( + DpmMockContext.CALLER_USER_HANDLE, + PasswordMetrics.computeForPassword("asdf")); + dpms.mUserPasswordMetrics.put( + parentUser.id, + PasswordMetrics.computeForPassword("parentUser")); + + assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity()); + } + private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) { final long ident = mServiceContext.binder.clearCallingIdentity(); mServiceContext.binder.callingUid = diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java index 3da61d69c742..4982d6e8817f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java @@ -26,7 +26,12 @@ public class DevicePolicyManagerTestable extends DevicePolicyManager { public DevicePolicyManagerTestable(DpmMockContext context, DevicePolicyManagerServiceTestable dpms) { - super(context, dpms, /* parentInstance = */ false); + this(context, dpms, /* parentInstance= */ false); + } + + public DevicePolicyManagerTestable(DpmMockContext context, + DevicePolicyManagerServiceTestable dpms, boolean parentInstance) { + super(context, dpms, parentInstance); this.dpms = dpms; } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index b4212808d585..e9bfa8f4e0c8 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -18,11 +18,21 @@ package com.android.server.display; import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import android.content.Context; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.Curve; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayViewport; +import android.hardware.display.DisplayedContentSample; +import android.hardware.display.DisplayedContentSamplingAttributes; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.input.InputManagerInternal; import android.os.Handler; @@ -31,9 +41,9 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.SurfaceControl; -import androidx.test.runner.AndroidJUnit4; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -49,17 +59,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Arrays; import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.mock; +import java.util.stream.LongStream; @SmallTest @RunWith(AndroidJUnit4.class) @@ -397,6 +398,43 @@ public class DisplayManagerServiceTest { displayManager.validateBrightnessConfiguration(null); } + /** + * Tests that collection of display color sampling results are sensible. + */ + @Test + public void testDisplayedContentSampling() { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mShortMockedInjector); + registerDefaultDisplays(displayManager); + + DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(0); + assertNotNull(ddi); + + DisplayedContentSamplingAttributes attr = + displayManager.getDisplayedContentSamplingAttributesInternal(0); + if (attr == null) return; //sampling not supported on device, skip remainder of test. + + boolean enabled = displayManager.setDisplayedContentSamplingEnabledInternal(0, true, 0, 0); + assertTrue(!enabled); + + displayManager.setDisplayedContentSamplingEnabledInternal(0, false, 0, 0); + DisplayedContentSample sample = displayManager.getDisplayedContentSampleInternal(0, 0, 0); + assertNotNull(sample); + + long numPixels = ddi.width * ddi.height * sample.getNumFrames(); + long[] samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL0); + assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels); + + samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL1); + assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels); + + samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL2); + assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels); + + samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL3); + assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels); + } + private void registerDefaultDisplays(DisplayManagerService displayManager) { Handler handler = displayManager.getDisplayHandler(); // Would prefer to call displayManager.onStart() directly here but it performs binderService diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java index c4c2ad926954..839b25f8491d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java @@ -212,7 +212,29 @@ public class CrossProfileAppsServiceImplTest { mIApplicationThread, PACKAGE_ONE, ACTIVITY_COMPONENT, - UserHandle.of(PRIMARY_USER))); + UserHandle.of(PRIMARY_USER).getIdentifier(), + true)); + + verify(mActivityTaskManagerInternal, never()) + .startActivityAsUser( + nullable(IApplicationThread.class), + anyString(), + any(Intent.class), + nullable(Bundle.class), + anyInt()); + } + + @Test + public void startAnyActivityAsUser_currentUser() { + assertThrows( + SecurityException.class, + () -> + mCrossProfileAppsServiceImpl.startActivityAsUser( + mIApplicationThread, + PACKAGE_ONE, + ACTIVITY_COMPONENT, + UserHandle.of(PRIMARY_USER).getIdentifier(), + false)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -234,7 +256,31 @@ public class CrossProfileAppsServiceImplTest { mIApplicationThread, PACKAGE_ONE, ACTIVITY_COMPONENT, - UserHandle.of(PROFILE_OF_PRIMARY_USER))); + UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), + true)); + + verify(mActivityTaskManagerInternal, never()) + .startActivityAsUser( + nullable(IApplicationThread.class), + anyString(), + any(Intent.class), + nullable(Bundle.class), + anyInt()); + } + + @Test + public void startAnyActivityAsUser_profile_notInstalled() { + mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false); + + assertThrows( + SecurityException.class, + () -> + mCrossProfileAppsServiceImpl.startActivityAsUser( + mIApplicationThread, + PACKAGE_ONE, + ACTIVITY_COMPONENT, + UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), + false)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -254,7 +300,29 @@ public class CrossProfileAppsServiceImplTest { mIApplicationThread, PACKAGE_TWO, ACTIVITY_COMPONENT, - UserHandle.of(PROFILE_OF_PRIMARY_USER))); + UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), + true)); + + verify(mActivityTaskManagerInternal, never()) + .startActivityAsUser( + nullable(IApplicationThread.class), + anyString(), + any(Intent.class), + nullable(Bundle.class), + anyInt()); + } + + @Test + public void startAnyActivityAsUser_profile_fakeCaller() { + assertThrows( + SecurityException.class, + () -> + mCrossProfileAppsServiceImpl.startActivityAsUser( + mIApplicationThread, + PACKAGE_TWO, + ACTIVITY_COMPONENT, + UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), + false)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -276,7 +344,31 @@ public class CrossProfileAppsServiceImplTest { mIApplicationThread, PACKAGE_ONE, ACTIVITY_COMPONENT, - UserHandle.of(PROFILE_OF_PRIMARY_USER))); + UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), + true)); + + verify(mActivityTaskManagerInternal, never()) + .startActivityAsUser( + nullable(IApplicationThread.class), + anyString(), + any(Intent.class), + nullable(Bundle.class), + anyInt()); + } + + @Test + public void startAnyActivityAsUser_profile_notExported() { + mActivityInfo.exported = false; + + assertThrows( + SecurityException.class, + () -> + mCrossProfileAppsServiceImpl.startActivityAsUser( + mIApplicationThread, + PACKAGE_ONE, + ACTIVITY_COMPONENT, + UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), + false)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -296,7 +388,29 @@ public class CrossProfileAppsServiceImplTest { mIApplicationThread, PACKAGE_ONE, new ComponentName(PACKAGE_TWO, "test"), - UserHandle.of(PROFILE_OF_PRIMARY_USER))); + UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), + true)); + + verify(mActivityTaskManagerInternal, never()) + .startActivityAsUser( + nullable(IApplicationThread.class), + anyString(), + any(Intent.class), + nullable(Bundle.class), + anyInt()); + } + + @Test + public void startAnyActivityAsUser_profile_anotherPackage() { + assertThrows( + SecurityException.class, + () -> + mCrossProfileAppsServiceImpl.startActivityAsUser( + mIApplicationThread, + PACKAGE_ONE, + new ComponentName(PACKAGE_TWO, "test"), + UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(), + false)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -316,7 +430,29 @@ public class CrossProfileAppsServiceImplTest { mIApplicationThread, PACKAGE_ONE, ACTIVITY_COMPONENT, - UserHandle.of(SECONDARY_USER))); + UserHandle.of(SECONDARY_USER).getIdentifier(), + true)); + + verify(mActivityTaskManagerInternal, never()) + .startActivityAsUser( + nullable(IApplicationThread.class), + anyString(), + any(Intent.class), + nullable(Bundle.class), + anyInt()); + } + + @Test + public void startAnyActivityAsUser_secondaryUser() { + assertThrows( + SecurityException.class, + () -> + mCrossProfileAppsServiceImpl.startActivityAsUser( + mIApplicationThread, + PACKAGE_ONE, + ACTIVITY_COMPONENT, + UserHandle.of(SECONDARY_USER).getIdentifier(), + false)); verify(mActivityTaskManagerInternal, never()) .startActivityAsUser( @@ -335,7 +471,8 @@ public class CrossProfileAppsServiceImplTest { mIApplicationThread, PACKAGE_ONE, ACTIVITY_COMPONENT, - UserHandle.of(PRIMARY_USER)); + UserHandle.of(PRIMARY_USER).getIdentifier(), + true); verify(mActivityTaskManagerInternal) .startActivityAsUser( diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 7bcb5719c357..823b7a5ee545 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -16,13 +16,13 @@ package com.android.server.pm; +import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.UserInfo; -import android.app.ActivityManager; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -162,6 +162,29 @@ public class UserManagerTest extends AndroidTestCase { } @MediumTest + public void testRemoveUserByHandle() { + UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST); + final UserHandle user = userInfo.getUserHandle(); + synchronized (mUserRemoveLock) { + mUserManager.removeUser(user); + long time = System.currentTimeMillis(); + while (mUserManager.getUserInfo(user.getIdentifier()) != null) { + try { + mUserRemoveLock.wait(REMOVE_CHECK_INTERVAL_MILLIS); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + return; + } + if (System.currentTimeMillis() - time > REMOVE_TIMEOUT_MILLIS) { + fail("Timeout waiting for removeUser. userId = " + user.getIdentifier()); + } + } + } + + assertFalse(findUser(userInfo.id)); + } + + @MediumTest public void testAddGuest() throws Exception { UserInfo userInfo1 = createUser("Guest 1", UserInfo.FLAG_GUEST); UserInfo userInfo2 = createUser("Guest 2", UserInfo.FLAG_GUEST); @@ -504,9 +527,21 @@ public class UserManagerTest extends AndroidTestCase { UserInfo user = createUser("User", 0); assertNotNull(user); // Switch to the user just created. - switchUser(user.id); + switchUser(user.id, null, true); // Switch back to the starting user. - switchUser(startUser); + switchUser(startUser, null, true); + } + + @LargeTest + public void testSwitchUserByHandle() { + ActivityManager am = getContext().getSystemService(ActivityManager.class); + final int startUser = am.getCurrentUser(); + UserInfo user = createUser("User", 0); + assertNotNull(user); + // Switch to the user just created. + switchUser(-1, user.getUserHandle(), false); + // Switch back to the starting user. + switchUser(-1, UserHandle.of(startUser), false); } @MediumTest @@ -544,10 +579,20 @@ public class UserManagerTest extends AndroidTestCase { } } - private void switchUser(int userId) { + /** + * @param userId value will be used to call switchUser(int) only if ignoreHandle is false. + * @param user value will be used to call switchUser(UserHandle) only if ignoreHandle is true. + * @param ignoreHandle if true, switchUser(int) will be called with the provided userId, + * else, switchUser(UserHandle) will be called with the provided user. + */ + private void switchUser(int userId, UserHandle user, boolean ignoreHandle) { synchronized (mUserSwitchLock) { ActivityManager am = getContext().getSystemService(ActivityManager.class); - am.switchUser(userId); + if (ignoreHandle) { + am.switchUser(userId); + } else { + am.switchUser(user); + } long time = System.currentTimeMillis(); try { mUserSwitchLock.wait(SWITCH_USER_TIMEOUT_MILLIS); @@ -556,7 +601,8 @@ public class UserManagerTest extends AndroidTestCase { return; } if (System.currentTimeMillis() - time > SWITCH_USER_TIMEOUT_MILLIS) { - fail("Timeout waiting for the user switch to u" + userId); + fail("Timeout waiting for the user switch to u" + + (ignoreHandle ? userId : user.getIdentifier())); } } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index dad7b93e822e..fd07cb046fb5 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -18,10 +18,14 @@ package com.android.server.pm.dex; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -41,6 +45,10 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.pm.Installer; +import dalvik.system.DelegateLastClassLoader; +import dalvik.system.PathClassLoader; +import dalvik.system.VMRuntime; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -50,10 +58,6 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.quality.Strictness; -import dalvik.system.DelegateLastClassLoader; -import dalvik.system.PathClassLoader; -import dalvik.system.VMRuntime; - import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -129,6 +133,9 @@ public class DexManagerTests { // Package is not used by others, so we should get nothing back. assertNoUseInfo(mFooUser0); + + // A package loading its own code is not stored as DCL. + assertNoDclInfo(mFooUser0); } @Test @@ -140,6 +147,8 @@ public class DexManagerTests { PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertIsUsedByOtherApps(mBarUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); + + assertHasDclInfo(mBarUser0, mFooUser0, mBarUser0.getBaseAndSplitDexPaths()); } @Test @@ -152,6 +161,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); + + assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); } @Test @@ -164,6 +175,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mBarUser0, pui, false); assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0); + + assertHasDclInfo(mBarUser0, mFooUser0, barSecondaries); } @Test @@ -200,9 +213,10 @@ public class DexManagerTests { } @Test - public void testPackageUseInfoNotFound() { + public void testNoNotify() { // Assert we don't get back data we did not previously record. assertNoUseInfo(mFooUser0); + assertNoDclInfo(mFooUser0); } @Test @@ -210,6 +224,7 @@ public class DexManagerTests { // Notifying with an invalid ISA should be ignored. notifyDexLoad(mInvalidIsa, mInvalidIsa.getSecondaryDexPaths(), mUser0); assertNoUseInfo(mInvalidIsa); + assertNoDclInfo(mInvalidIsa); } @Test @@ -218,6 +233,7 @@ public class DexManagerTests { // register in DexManager#load should be ignored. notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0); assertNoUseInfo(mDoesNotExist); + assertNoDclInfo(mDoesNotExist); } @Test @@ -226,6 +242,8 @@ public class DexManagerTests { // Request should be ignored. notifyDexLoad(mBarUser1, mBarUser0.getSecondaryDexPaths(), mUser1); assertNoUseInfo(mBarUser1); + + assertNoDclInfo(mBarUser1); } @Test @@ -235,6 +253,10 @@ public class DexManagerTests { // still check that nothing goes unexpected in DexManager. notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser1); assertNoUseInfo(mBarUser1); + assertNoUseInfo(mFooUser0); + + assertNoDclInfo(mBarUser1); + assertNoDclInfo(mFooUser0); } @Test @@ -247,6 +269,7 @@ public class DexManagerTests { // is trying to load something from it we should not find it. notifyDexLoad(mFooUser0, newSecondaries, mUser0); assertNoUseInfo(newPackage); + assertNoDclInfo(newPackage); // Notify about newPackage install and let mFoo load its dexes. mDexManager.notifyPackageInstalled(newPackage.mPackageInfo, mUser0); @@ -257,6 +280,7 @@ public class DexManagerTests { assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0); + assertHasDclInfo(newPackage, mFooUser0, newSecondaries); } @Test @@ -273,6 +297,7 @@ public class DexManagerTests { assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0); + assertHasDclInfo(newPackage, newPackage, newSecondaries); } @Test @@ -305,6 +330,7 @@ public class DexManagerTests { // We shouldn't find yet the new split as we didn't notify the package update. notifyDexLoad(mFooUser0, newSplits, mUser0); assertNoUseInfo(mBarUser0); + assertNoDclInfo(mBarUser0); // Notify that bar is updated. splitSourceDirs will contain the updated path. mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(), @@ -314,8 +340,8 @@ public class DexManagerTests { // Now, when the split is loaded we will find it and we should mark Bar as usedByOthers. notifyDexLoad(mFooUser0, newSplits, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); assertIsUsedByOtherApps(newSplits, pui, true); + assertHasDclInfo(mBarUser0, mFooUser0, newSplits); } @Test @@ -326,11 +352,15 @@ public class DexManagerTests { mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), mUser0); - // Bar should not be around since it was removed for all users. + // Data for user 1 should still be present PackageUseInfo pui = getPackageUseInfo(mBarUser1); - assertNotNull(pui); assertSecondaryUse(mBarUser1, pui, mBarUser1.getSecondaryDexPaths(), /*isUsedByOtherApps*/false, mUser1); + assertHasDclInfo(mBarUser1, mBarUser1, mBarUser1.getSecondaryDexPaths()); + + // But not user 0 + assertNoUseInfo(mBarUser0, mUser0); + assertNoDclInfo(mBarUser0, mUser0); } @Test @@ -349,6 +379,8 @@ public class DexManagerTests { PackageUseInfo pui = getPackageUseInfo(mFooUser0); assertIsUsedByOtherApps(mFooUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); + + assertNoDclInfo(mFooUser0); } @Test @@ -362,6 +394,7 @@ public class DexManagerTests { // Foo should not be around since all its secondary dex info were deleted // and it is not used by other apps. assertNoUseInfo(mFooUser0); + assertNoDclInfo(mFooUser0); } @Test @@ -374,6 +407,7 @@ public class DexManagerTests { // Bar should not be around since it was removed for all users. assertNoUseInfo(mBarUser0); + assertNoDclInfo(mBarUser0); } @Test @@ -383,6 +417,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0); // The dex file should not be recognized as a package. assertFalse(mDexManager.hasInfoOnPackage(frameworkDex)); + assertNull(mDexManager.getPackageDynamicCodeInfo(frameworkDex)); } @Test @@ -395,6 +430,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); + + assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); } @Test @@ -402,7 +439,12 @@ public class DexManagerTests { List<String> secondaries = mBarUser0UnsupportedClassLoader.getSecondaryDexPaths(); notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); + // We don't record the dex usage assertNoUseInfo(mBarUser0UnsupportedClassLoader); + + // But we do record this as an intance of dynamic code loading + assertHasDclInfo( + mBarUser0UnsupportedClassLoader, mBarUser0UnsupportedClassLoader, secondaries); } @Test @@ -414,6 +456,8 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, classLoaders, classPaths, mUser0); assertNoUseInfo(mBarUser0); + + assertHasDclInfo(mBarUser0, mBarUser0, mBarUser0.getSecondaryDexPaths()); } @Test @@ -421,6 +465,7 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, null, mUser0); assertNoUseInfo(mBarUser0); + assertNoDclInfo(mBarUser0); } @Test @@ -455,12 +500,14 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); + assertHasDclInfo(mBarUser0, mBarUser0, secondaries); // Record bar secondaries again with an unsupported class loader. This should not change the // context. notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); pui = getPackageUseInfo(mBarUser0); assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); + assertHasDclInfo(mBarUser0, mBarUser0, secondaries); } @Test @@ -533,13 +580,53 @@ public class DexManagerTests { private PackageUseInfo getPackageUseInfo(TestData testData) { assertTrue(mDexManager.hasInfoOnPackage(testData.getPackageName())); - return mDexManager.getPackageUseInfoOrDefault(testData.getPackageName()); + PackageUseInfo pui = mDexManager.getPackageUseInfoOrDefault(testData.getPackageName()); + assertNotNull(pui); + return pui; } private void assertNoUseInfo(TestData testData) { assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName())); } + private void assertNoUseInfo(TestData testData, int userId) { + if (!mDexManager.hasInfoOnPackage(testData.getPackageName())) { + return; + } + PackageUseInfo pui = getPackageUseInfo(testData); + for (DexUseInfo dexUseInfo : pui.getDexUseInfoMap().values()) { + assertNotEquals(userId, dexUseInfo.getOwnerUserId()); + } + } + + private void assertNoDclInfo(TestData testData) { + assertNull(mDexManager.getPackageDynamicCodeInfo(testData.getPackageName())); + } + + private void assertNoDclInfo(TestData testData, int userId) { + PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(testData.getPackageName()); + if (info == null) { + return; + } + + for (DynamicCodeFile fileInfo : info.mFileUsageMap.values()) { + assertNotEquals(userId, fileInfo.mUserId); + } + } + + private void assertHasDclInfo(TestData owner, TestData loader, List<String> paths) { + PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(owner.getPackageName()); + assertNotNull("No DCL data for owner " + owner.getPackageName(), info); + for (String path : paths) { + DynamicCodeFile fileInfo = info.mFileUsageMap.get(path); + assertNotNull("No DCL data for path " + path, fileInfo); + assertEquals(PackageDynamicCodeLoading.FILE_TYPE_DEX, fileInfo.mFileType); + assertEquals(owner.mUserId, fileInfo.mUserId); + assertTrue("No DCL data for loader " + loader.getPackageName(), + fileInfo.mLoadingPackages.contains(loader.getPackageName())); + } + } + private static PackageInfo getMockPackageInfo(String packageName, int userId) { PackageInfo pi = new PackageInfo(); pi.packageName = packageName; @@ -563,11 +650,13 @@ public class DexManagerTests { private final PackageInfo mPackageInfo; private final String mLoaderIsa; private final String mClassLoader; + private final int mUserId; private TestData(String packageName, String loaderIsa, int userId, String classLoader) { mPackageInfo = getMockPackageInfo(packageName, userId); mLoaderIsa = loaderIsa; mClassLoader = classLoader; + mUserId = userId; } private TestData(String packageName, String loaderIsa, int userId) { @@ -603,9 +692,7 @@ public class DexManagerTests { List<String> getBaseAndSplitDexPaths() { List<String> paths = new ArrayList<>(); paths.add(mPackageInfo.applicationInfo.sourceDir); - for (String split : mPackageInfo.applicationInfo.splitSourceDirs) { - paths.add(split); - } + Collections.addAll(paths, mPackageInfo.applicationInfo.splitSourceDirs); return paths; } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java new file mode 100644 index 000000000000..eb4cc4e36616 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java @@ -0,0 +1,560 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.dex; + +import static com.android.server.pm.dex.PackageDynamicCodeLoading.escape; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.unescape; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertThrows; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile; +import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class PackageDynamicCodeLoadingTests { + + // Deliberately making a copy here since we're testing identity and + // string literals have a tendency to be identical. + private static final String TRIVIAL_STRING = new String("hello/world"); + private static final Entry[] NO_ENTRIES = {}; + private static final String[] NO_PACKAGES = {}; + + @Test + public void testRecord() { + Entry[] entries = { + new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1"), + new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package2"), + new Entry("owning.package1", "/path/file2", 'D', 5, "loading.package1"), + new Entry("owning.package2", "/path/file3", 'D', 0, "loading.package2"), + }; + + PackageDynamicCodeLoading info = makePackageDcl(entries); + assertHasEntries(info, entries); + } + + @Test + public void testRecord_returnsHasChanged() { + Entry owner1Path1Loader1 = + new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1"); + Entry owner1Path1Loader2 = + new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package2"); + Entry owner1Path2Loader1 = + new Entry("owning.package1", "/path/file2", 'D', 10, "loading.package1"); + Entry owner2Path1Loader1 = + new Entry("owning.package2", "/path/file1", 'D', 10, "loading.package2"); + + PackageDynamicCodeLoading info = new PackageDynamicCodeLoading(); + + assertTrue(record(info, owner1Path1Loader1)); + assertFalse(record(info, owner1Path1Loader1)); + + assertTrue(record(info, owner1Path1Loader2)); + assertFalse(record(info, owner1Path1Loader2)); + + assertTrue(record(info, owner1Path2Loader1)); + assertFalse(record(info, owner1Path2Loader1)); + + assertTrue(record(info, owner2Path1Loader1)); + assertFalse(record(info, owner2Path1Loader1)); + + assertHasEntries(info, + owner1Path1Loader1, owner1Path1Loader2, owner1Path2Loader1, owner2Path1Loader1); + } + + @Test + public void testRecord_changeUserForFile_throws() { + Entry entry1 = new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1"); + Entry entry2 = new Entry("owning.package1", "/path/file1", 'D', 20, "loading.package1"); + + PackageDynamicCodeLoading info = makePackageDcl(entry1); + + assertThrows(() -> record(info, entry2)); + assertHasEntries(info, entry1); + } + + @Test + public void testRecord_badFileType_throws() { + Entry entry = new Entry("owning.package", "/path/file", 'Z', 10, "loading.package"); + PackageDynamicCodeLoading info = new PackageDynamicCodeLoading(); + + assertThrows(() -> record(info, entry)); + } + + @Test + public void testClear() { + Entry[] entries = { + new Entry("owner1", "file1", 'D', 10, "loader1"), + new Entry("owner2", "file2", 'D', 20, "loader2"), + }; + PackageDynamicCodeLoading info = makePackageDcl(entries); + + info.clear(); + assertHasEntries(info, NO_ENTRIES); + } + + @Test + public void testRemovePackage_present() { + Entry other = new Entry("other", "file", 'D', 0, "loader"); + Entry[] entries = { + new Entry("owner", "file1", 'D', 10, "loader1"), + new Entry("owner", "file2", 'D', 20, "loader2"), + other + }; + PackageDynamicCodeLoading info = makePackageDcl(entries); + + assertTrue(info.removePackage("owner")); + assertHasEntries(info, other); + assertHasPackages(info, "other"); + } + + @Test + public void testRemovePackage_notPresent() { + Entry[] entries = { new Entry("owner", "file", 'D', 0, "loader") }; + PackageDynamicCodeLoading info = makePackageDcl(entries); + + assertFalse(info.removePackage("other")); + assertHasEntries(info, entries); + } + + @Test + public void testRemoveUserPackage_notPresent() { + Entry[] entries = { new Entry("owner", "file", 'D', 0, "loader") }; + PackageDynamicCodeLoading info = makePackageDcl(entries); + + assertFalse(info.removeUserPackage("other", 0)); + assertHasEntries(info, entries); + } + + @Test + public void testRemoveUserPackage_presentWithNoOtherUsers() { + Entry other = new Entry("other", "file", 'D', 0, "loader"); + Entry[] entries = { + new Entry("owner", "file1", 'D', 0, "loader1"), + new Entry("owner", "file2", 'D', 0, "loader2"), + other + }; + PackageDynamicCodeLoading info = makePackageDcl(entries); + + assertTrue(info.removeUserPackage("owner", 0)); + assertHasEntries(info, other); + assertHasPackages(info, "other"); + } + + @Test + public void testRemoveUserPackage_presentWithUsers() { + Entry other = new Entry("owner", "file", 'D', 1, "loader"); + Entry[] entries = { + new Entry("owner", "file1", 'D', 0, "loader1"), + new Entry("owner", "file2", 'D', 0, "loader2"), + other + }; + PackageDynamicCodeLoading info = makePackageDcl(entries); + + assertTrue(info.removeUserPackage("owner", 0)); + assertHasEntries(info, other); + } + + @Test + public void testRemoveFile_present() { + Entry[] entries = { + new Entry("package1", "file1", 'D', 0, "loader1"), + new Entry("package1", "file2", 'D', 0, "loader1"), + new Entry("package2", "file1", 'D', 0, "loader2"), + }; + Entry[] expectedSurvivors = { + new Entry("package1", "file2", 'D', 0, "loader1"), + new Entry("package2", "file1", 'D', 0, "loader2"), + }; + PackageDynamicCodeLoading info = makePackageDcl(entries); + + assertTrue(info.removeFile("package1", "file1", 0)); + assertHasEntries(info, expectedSurvivors); + } + + @Test + public void testRemoveFile_onlyEntry() { + Entry[] entries = { + new Entry("package1", "file1", 'D', 0, "loader1"), + }; + PackageDynamicCodeLoading info = makePackageDcl(entries); + + assertTrue(info.removeFile("package1", "file1", 0)); + assertHasEntries(info, NO_ENTRIES); + assertHasPackages(info, NO_PACKAGES); + } + + @Test + public void testRemoveFile_notPresent() { + Entry[] entries = { + new Entry("package1", "file2", 'D', 0, "loader1"), + }; + PackageDynamicCodeLoading info = makePackageDcl(entries); + + assertFalse(info.removeFile("package1", "file1", 0)); + assertHasEntries(info, entries); + } + + @Test + public void testRemoveFile_wrongUser() { + Entry[] entries = { + new Entry("package1", "file1", 'D', 10, "loader1"), + }; + PackageDynamicCodeLoading info = makePackageDcl(entries); + + assertFalse(info.removeFile("package1", "file1", 0)); + assertHasEntries(info, entries); + } + + @Test + public void testSyncData() { + Map<String, Set<Integer>> packageToUsersMap = ImmutableMap.of( + "package1", ImmutableSet.of(10, 20), + "package2", ImmutableSet.of(20)); + + Entry[] entries = { + new Entry("deleted.packaged", "file1", 'D', 10, "package1"), + new Entry("package1", "file2", 'D', 20, "package2"), + new Entry("package1", "file3", 'D', 10, "package2"), + new Entry("package1", "file3", 'D', 10, "deleted.package"), + new Entry("package2", "file4", 'D', 20, "deleted.package"), + }; + + Entry[] expectedSurvivors = { + new Entry("package1", "file2", 'D', 20, "package2"), + }; + + PackageDynamicCodeLoading info = makePackageDcl(entries); + info.syncData(packageToUsersMap); + assertHasEntries(info, expectedSurvivors); + assertHasPackages(info, "package1"); + } + + @Test + public void testRead_onlyHeader_emptyResult() throws Exception { + assertHasEntries(read("DCL1"), NO_ENTRIES); + } + + @Test + public void testRead_noHeader_throws() { + assertThrows(IOException.class, () -> read("")); + } + + @Test + public void testRead_wrongHeader_throws() { + assertThrows(IOException.class, () -> read("DCL2")); + } + + @Test + public void testRead_oneEntry() throws Exception { + String inputText = "" + + "DCL1\n" + + "P:owning.package\n" + + "D:10:loading.package:/path/fi\\\\le\n"; + assertHasEntries(read(inputText), + new Entry("owning.package", "/path/fi\\le", 'D', 10, "loading.package")); + } + + @Test + public void testRead_emptyPackage() throws Exception { + String inputText = "" + + "DCL1\n" + + "P:owning.package\n"; + PackageDynamicCodeLoading info = read(inputText); + assertHasEntries(info, NO_ENTRIES); + assertHasPackages(info, NO_PACKAGES); + } + + @Test + public void testRead_complex() throws Exception { + String inputText = "" + + "DCL1\n" + + "P:owning.package1\n" + + "D:10:loading.package1,loading.package2:/path/file1\n" + + "D:5:loading.package1:/path/file2\n" + + "P:owning.package2\n" + + "D:0:loading.package2:/path/file3"; + assertHasEntries(read(inputText), + new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1"), + new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package2"), + new Entry("owning.package1", "/path/file2", 'D', 5, "loading.package1"), + new Entry("owning.package2", "/path/file3", 'D', 0, "loading.package2")); + } + + @Test + public void testRead_missingPackageLine_throws() { + String inputText = "" + + "DCL1\n" + + "D:10:loading.package:/path/file\n"; + assertThrows(IOException.class, () -> read(inputText)); + } + + @Test + public void testRead_malformedFile_throws() { + String inputText = "" + + "DCL1\n" + + "P:owning.package\n" + + "Hello world!\n"; + assertThrows(IOException.class, () -> read(inputText)); + } + + @Test + public void testRead_badFileType_throws() { + String inputText = "" + + "DCL1\n" + + "P:owning.package\n" + + "X:10:loading.package:/path/file\n"; + assertThrows(IOException.class, () -> read(inputText)); + } + + @Test + public void testRead_badUserId_throws() { + String inputText = "" + + "DCL1\n" + + "P:owning.package\n" + + "D:999999999999999999:loading.package:/path/file\n"; + assertThrows(IOException.class, () -> read(inputText)); + } + + @Test + public void testRead_missingPackages_throws() { + String inputText = "" + + "DCL1\n" + + "P:owning.package\n" + + "D:1:,:/path/file\n"; + assertThrows(IOException.class, () -> read(inputText)); + } + + @Test + public void testWrite_empty() throws Exception { + assertEquals("DCL1\n", write(NO_ENTRIES)); + } + + @Test + public void testWrite_oneEntry() throws Exception { + String expected = "" + + "DCL1\n" + + "P:owning.package\n" + + "D:10:loading.package:/path/fi\\\\le\n"; + String actual = write( + new Entry("owning.package", "/path/fi\\le", 'D', 10, "loading.package")); + assertEquals(expected, actual); + } + + @Test + public void testWrite_complex_roundTrips() throws Exception { + // There isn't a canonical order for the output in the presence of multiple items. + // So we just check that if we read back what we write we end up where we started. + Entry[] entries = { + new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1"), + new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package2"), + new Entry("owning.package1", "/path/file2", 'D', 5, "loading.package1"), + new Entry("owning.package2", "/path/fi\\le3", 'D', 0, "loading.package2") + }; + assertHasEntries(read(write(entries)), entries); + } + + @Test + public void testWrite_failure_throws() { + PackageDynamicCodeLoading info = makePackageDcl( + new Entry("owning.package", "/path/fi\\le", 'D', 10, "loading.package")); + assertThrows(IOException.class, () -> info.write(new ThrowingOutputStream())); + } + + @Test + public void testEscape_trivialCase_returnsSameString() { + assertSame(TRIVIAL_STRING, escape(TRIVIAL_STRING)); + } + + @Test + public void testEscape() { + String input = "backslash\\newline\nreturn\r"; + String expected = "backslash\\\\newline\\nreturn\\r"; + assertEquals(expected, escape(input)); + } + + @Test + public void testUnescape_trivialCase_returnsSameString() throws Exception { + assertSame(TRIVIAL_STRING, unescape(TRIVIAL_STRING)); + } + + @Test + public void testUnescape() throws Exception { + String input = "backslash\\\\newline\\nreturn\\r"; + String expected = "backslash\\newline\nreturn\r"; + assertEquals(expected, unescape(input)); + } + + @Test + public void testUnescape_badEscape_throws() { + assertThrows(IOException.class, () -> unescape("this is \\bad")); + } + + @Test + public void testUnescape_trailingBackslash_throws() { + assertThrows(IOException.class, () -> unescape("don't do this\\")); + } + + @Test + public void testEscapeUnescape_roundTrips() throws Exception { + assertRoundTripsWithEscape("foo"); + assertRoundTripsWithEscape("\\\\\n\n\r"); + assertRoundTripsWithEscape("\\a\\b\\"); + assertRoundTripsWithEscape("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"); + } + + private void assertRoundTripsWithEscape(String original) throws Exception { + assertEquals(original, unescape(escape(original))); + } + + private boolean record(PackageDynamicCodeLoading info, Entry entry) { + return info.record(entry.mOwningPackage, entry.mPath, entry.mFileType, entry.mUserId, + entry.mLoadingPackage); + } + + private PackageDynamicCodeLoading read(String inputText) throws Exception { + ByteArrayInputStream inputStream = + new ByteArrayInputStream(inputText.getBytes(UTF_8)); + + PackageDynamicCodeLoading info = new PackageDynamicCodeLoading(); + info.read(inputStream); + + return info; + } + + private String write(Entry... entries) throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + makePackageDcl(entries).write(output); + return new String(output.toByteArray(), UTF_8); + } + + private Set<Entry> entriesFrom(PackageDynamicCodeLoading info) { + ImmutableSet.Builder<Entry> entries = ImmutableSet.builder(); + for (String owningPackage : info.getAllPackagesWithDynamicCodeLoading()) { + PackageDynamicCode packageInfo = info.getPackageDynamicCodeInfo(owningPackage); + Map<String, DynamicCodeFile> usageMap = packageInfo.mFileUsageMap; + for (Map.Entry<String, DynamicCodeFile> fileEntry : usageMap.entrySet()) { + String path = fileEntry.getKey(); + DynamicCodeFile fileInfo = fileEntry.getValue(); + for (String loadingPackage : fileInfo.mLoadingPackages) { + entries.add(new Entry(owningPackage, path, fileInfo.mFileType, fileInfo.mUserId, + loadingPackage)); + } + } + } + + return entries.build(); + } + + private PackageDynamicCodeLoading makePackageDcl(Entry... entries) { + PackageDynamicCodeLoading result = new PackageDynamicCodeLoading(); + for (Entry entry : entries) { + result.record(entry.mOwningPackage, entry.mPath, entry.mFileType, entry.mUserId, + entry.mLoadingPackage); + } + return result; + + } + + private void assertHasEntries(PackageDynamicCodeLoading info, Entry... expected) { + assertEquals(ImmutableSet.copyOf(expected), entriesFrom(info)); + } + + private void assertHasPackages(PackageDynamicCodeLoading info, String... expected) { + assertEquals(ImmutableSet.copyOf(expected), info.getAllPackagesWithDynamicCodeLoading()); + } + + /** + * Immutable representation of one entry in the dynamic code loading data (one package + * owning one file loaded by one package). Has well-behaved equality, hash and toString + * for ease of use in assertions. + */ + private static class Entry { + private final String mOwningPackage; + private final String mPath; + private final char mFileType; + private final int mUserId; + private final String mLoadingPackage; + + private Entry(String owningPackage, String path, char fileType, int userId, + String loadingPackage) { + mOwningPackage = owningPackage; + mPath = path; + mFileType = fileType; + mUserId = userId; + mLoadingPackage = loadingPackage; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Entry that = (Entry) o; + return mFileType == that.mFileType + && mUserId == that.mUserId + && Objects.equals(mOwningPackage, that.mOwningPackage) + && Objects.equals(mPath, that.mPath) + && Objects.equals(mLoadingPackage, that.mLoadingPackage); + } + + @Override + public int hashCode() { + return Objects.hash(mOwningPackage, mPath, mFileType, mUserId, mLoadingPackage); + } + + @Override + public String toString() { + return "Entry(" + + "\"" + mOwningPackage + '"' + + ", \"" + mPath + '"' + + ", '" + mFileType + '\'' + + ", " + mUserId + + ", \"" + mLoadingPackage + '\"' + + ')'; + } + } + + private static class ThrowingOutputStream extends OutputStream { + @Override + public void write(int b) throws IOException { + throw new IOException("Intentional failure"); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java index 77f6a23c26b1..acf511e38738 100644 --- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import android.content.Context; -import android.os.Handler; import android.os.PowerManager; import android.os.PowerManager.ServiceType; import android.os.PowerSaveState; @@ -175,7 +174,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase { @SmallTest public void testGetBatterySaverPolicy_PolicyQuickDoze_DefaultValueCorrect() { - testServiceDefaultValue_Off(ServiceType.QUICK_DOZE); + testServiceDefaultValue_On(ServiceType.QUICK_DOZE); } @SmallTest diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 8ac29303268b..561c61fb979f 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -37,6 +37,7 @@ import android.os.PowerSaveState; import android.os.SystemClock; import android.os.SystemProperties; import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import com.android.internal.app.IBatteryStats; @@ -193,4 +194,19 @@ public class PowerManagerServiceTest extends AndroidTestCase { assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_ASLEEP); } + + @MediumTest + public void testWasDeviceIdleFor_true() { + int interval = 1000; + mService.onUserActivity(); + SystemClock.sleep(interval); + assertThat(mService.wasDeviceIdleForInternal(interval)).isTrue(); + } + + @SmallTest + public void testWasDeviceIdleFor_false() { + int interval = 1000; + mService.onUserActivity(); + assertThat(mService.wasDeviceIdleForInternal(interval)).isFalse(); + } } diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java index c1963da3b3af..94d293efd204 100644 --- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java @@ -281,6 +281,10 @@ public class ThermalManagerServiceTest { mFakeHal.mCallback.onValues(newSkin); verify(mIPowerManagerMock, timeout(CALLBACK_TIMEOUT_MILLI_SEC) .times(1)).shutdown(false, PowerManager.SHUTDOWN_THERMAL_STATE, false); + Temperature newBattery = new Temperature(60, Temperature.TYPE_BATTERY, "batt", status); + mFakeHal.mCallback.onValues(newBattery); + verify(mIPowerManagerMock, timeout(CALLBACK_TIMEOUT_MILLI_SEC) + .times(1)).shutdown(false, PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE, false); } @Test diff --git a/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java b/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java new file mode 100644 index 000000000000..00a62a300487 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2018 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.signedconfig; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.fail; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; + +import android.util.ArrayMap; + +import androidx.test.runner.AndroidJUnit4; + +import com.google.common.collect.Sets; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + + +/** + * Tests for {@link SignedConfig} + */ +@RunWith(AndroidJUnit4.class) +public class SignedConfigTest { + + private static Set<String> setOf(String... values) { + return Sets.newHashSet(values); + } + + private static <K, V> Map<K, V> mapOf(Object... keyValuePairs) { + if (keyValuePairs.length % 2 != 0) { + throw new IllegalArgumentException(); + } + final int len = keyValuePairs.length / 2; + ArrayMap<K, V> m = new ArrayMap<>(len); + for (int i = 0; i < len; ++i) { + m.put((K) keyValuePairs[i * 2], (V) keyValuePairs[(i * 2) + 1]); + } + return Collections.unmodifiableMap(m); + + } + + + @Test + public void testParsePerSdkConfigSdkMinMax() throws JSONException, InvalidConfigException { + JSONObject json = new JSONObject("{\"min_sdk\":2, \"max_sdk\": 3, \"values\": {}}"); + SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, emptySet(), + emptyMap()); + assertThat(config.minSdk).isEqualTo(2); + assertThat(config.maxSdk).isEqualTo(3); + } + + @Test + public void testParsePerSdkConfigNoMinSdk() throws JSONException { + JSONObject json = new JSONObject("{\"max_sdk\": 3, \"values\": {}}"); + try { + SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); + fail("Expected InvalidConfigException or JSONException"); + } catch (JSONException | InvalidConfigException e) { + // expected + } + } + + @Test + public void testParsePerSdkConfigNoMaxSdk() throws JSONException { + JSONObject json = new JSONObject("{\"min_sdk\": 1, \"values\": {}}"); + try { + SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); + fail("Expected InvalidConfigException or JSONException"); + } catch (JSONException | InvalidConfigException e) { + // expected + } + } + + @Test + public void testParsePerSdkConfigNoValues() throws JSONException { + JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 3}"); + try { + SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); + fail("Expected InvalidConfigException or JSONException"); + } catch (JSONException | InvalidConfigException e) { + // expected + } + } + + @Test + public void testParsePerSdkConfigSdkNullMinSdk() throws JSONException { + JSONObject json = new JSONObject("{\"min_sdk\":null, \"max_sdk\": 3, \"values\": {}}"); + try { + SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); + fail("Expected InvalidConfigException or JSONException"); + } catch (JSONException | InvalidConfigException e) { + // expected + } + } + + @Test + public void testParsePerSdkConfigSdkNullMaxSdk() throws JSONException { + JSONObject json = new JSONObject("{\"min_sdk\":1, \"max_sdk\": null, \"values\": {}}"); + try { + SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); + fail("Expected InvalidConfigException or JSONException"); + } catch (JSONException | InvalidConfigException e) { + // expected + } + } + + @Test + public void testParsePerSdkConfigNullValues() throws JSONException { + JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 3, \"values\": null}"); + try { + SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); + fail("Expected InvalidConfigException or JSONException"); + } catch (JSONException | InvalidConfigException e) { + // expected + } + } + + @Test + public void testParsePerSdkConfigZeroValues() + throws JSONException, InvalidConfigException { + JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 3, \"values\": {}}"); + SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"), + emptyMap()); + assertThat(config.values).hasSize(0); + } + + @Test + public void testParsePerSdkConfigSingleKey() + throws JSONException, InvalidConfigException { + JSONObject json = new JSONObject( + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}"); + SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"), + emptyMap()); + assertThat(config.values).containsExactly("a", "1"); + } + + @Test + public void testParsePerSdkConfigSingleKeyNullValue() + throws JSONException, InvalidConfigException { + JSONObject json = new JSONObject( + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": null}}"); + SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"), + emptyMap()); + assertThat(config.values.keySet()).containsExactly("a"); + assertThat(config.values.get("a")).isNull(); + } + + @Test + public void testParsePerSdkConfigMultiKeys() + throws JSONException, InvalidConfigException { + JSONObject json = new JSONObject( + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\", \"c\": \"2\"}}"); + SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig( + json, setOf("a", "b", "c"), emptyMap()); + assertThat(config.values).containsExactly("a", "1", "c", "2"); + } + + @Test + public void testParsePerSdkConfigSingleKeyNotAllowed() throws JSONException { + JSONObject json = new JSONObject( + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}"); + try { + SignedConfig.parsePerSdkConfig(json, setOf("b"), emptyMap()); + fail("Expected InvalidConfigException or JSONException"); + } catch (JSONException | InvalidConfigException e) { + // expected + } + } + + @Test + public void testParsePerSdkConfigSingleKeyWithMap() + throws JSONException, InvalidConfigException { + JSONObject json = new JSONObject( + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}"); + SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"), + mapOf("a", mapOf("1", "one"))); + assertThat(config.values).containsExactly("a", "one"); + } + + @Test + public void testParsePerSdkConfigSingleKeyWithMapInvalidValue() throws JSONException { + JSONObject json = new JSONObject( + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"2\"}}"); + try { + SignedConfig.parsePerSdkConfig(json, setOf("b"), mapOf("a", mapOf("1", "one"))); + fail("Expected InvalidConfigException"); + } catch (InvalidConfigException e) { + // expected + } + } + + @Test + public void testParsePerSdkConfigMultiKeysWithMap() + throws JSONException, InvalidConfigException { + JSONObject json = new JSONObject( + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\", \"b\": \"1\"}}"); + SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"), + mapOf("a", mapOf("1", "one"))); + assertThat(config.values).containsExactly("a", "one", "b", "1"); + } + + @Test + public void testParsePerSdkConfigSingleKeyWithMapToNull() + throws JSONException, InvalidConfigException { + JSONObject json = new JSONObject( + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": \"1\"}}"); + SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"), + mapOf("a", mapOf("1", null))); + assertThat(config.values).containsExactly("a", null); + } + + @Test + public void testParsePerSdkConfigSingleKeyWithMapFromNull() + throws JSONException, InvalidConfigException { + assertThat(mapOf(null, "allitnil")).containsExactly(null, "allitnil"); + assertThat(mapOf(null, "allitnil").containsKey(null)).isTrue(); + JSONObject json = new JSONObject( + "{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {\"a\": null}}"); + SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"), + mapOf("a", mapOf(null, "allitnil"))); + assertThat(config.values).containsExactly("a", "allitnil"); + } + + @Test + public void testParsePerSdkConfigValuesInvalid() throws JSONException { + JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": \"foo\"}"); + try { + SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); + fail("Expected InvalidConfigException or JSONException"); + } catch (JSONException | InvalidConfigException e) { + // expected + } + } + + @Test + public void testParsePerSdkConfigConfigEntryInvalid() throws JSONException { + JSONObject json = new JSONObject("{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": [1, 2]}"); + try { + SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); + fail("Expected InvalidConfigException or JSONException"); + } catch (JSONException | InvalidConfigException e) { + // expected + } + } + + @Test + public void testParseVersion() throws InvalidConfigException { + SignedConfig config = SignedConfig.parse( + "{\"version\": 1, \"config\": []}", emptySet(), emptyMap()); + assertThat(config.version).isEqualTo(1); + } + + @Test + public void testParseVersionInvalid() { + try { + SignedConfig.parse("{\"version\": \"notanint\", \"config\": []}", emptySet(), + emptyMap()); + fail("Expected InvalidConfigException"); + } catch (InvalidConfigException e) { + //expected + } + } + + @Test + public void testParseNoVersion() { + try { + SignedConfig.parse("{\"config\": []}", emptySet(), emptyMap()); + fail("Expected InvalidConfigException"); + } catch (InvalidConfigException e) { + //expected + } + } + + @Test + public void testParseNoConfig() { + try { + SignedConfig.parse("{\"version\": 1}", emptySet(), emptyMap()); + fail("Expected InvalidConfigException"); + } catch (InvalidConfigException e) { + //expected + } + } + + @Test + public void testParseConfigNull() { + try { + SignedConfig.parse("{\"version\": 1, \"config\": null}", emptySet(), emptyMap()); + fail("Expected InvalidConfigException"); + } catch (InvalidConfigException e) { + //expected + } + } + + @Test + public void testParseVersionNull() { + try { + SignedConfig.parse("{\"version\": null, \"config\": []}", emptySet(), emptyMap()); + fail("Expected InvalidConfigException"); + } catch (InvalidConfigException e) { + //expected + } + } + + @Test + public void testParseConfigInvalidEntry() { + try { + SignedConfig.parse("{\"version\": 1, \"config\": [{}]}", emptySet(), emptyMap()); + fail("Expected InvalidConfigException"); + } catch (InvalidConfigException e) { + //expected + } + } + + @Test + public void testParseSdkConfigSingle() throws InvalidConfigException { + SignedConfig config = SignedConfig.parse( + "{\"version\": 1, \"config\":[{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {}}]}", + emptySet(), emptyMap()); + assertThat(config.perSdkConfig).hasSize(1); + } + + @Test + public void testParseSdkConfigMultiple() throws InvalidConfigException { + SignedConfig config = SignedConfig.parse( + "{\"version\": 1, \"config\":[{\"min_sdk\": 1, \"max_sdk\": 1, \"values\": {}}, " + + "{\"min_sdk\": 2, \"max_sdk\": 2, \"values\": {}}]}", emptySet(), + emptyMap()); + assertThat(config.perSdkConfig).hasSize(2); + } + + @Test + public void testGetMatchingConfigFirst() { + SignedConfig.PerSdkConfig sdk1 = new SignedConfig.PerSdkConfig( + 1, 1, Collections.emptyMap()); + SignedConfig.PerSdkConfig sdk2 = new SignedConfig.PerSdkConfig( + 2, 2, Collections.emptyMap()); + SignedConfig config = new SignedConfig(0, Arrays.asList(sdk1, sdk2)); + assertThat(config.getMatchingConfig(1)).isEqualTo(sdk1); + } + + @Test + public void testGetMatchingConfigSecond() { + SignedConfig.PerSdkConfig sdk1 = new SignedConfig.PerSdkConfig( + 1, 1, Collections.emptyMap()); + SignedConfig.PerSdkConfig sdk2 = new SignedConfig.PerSdkConfig( + 2, 2, Collections.emptyMap()); + SignedConfig config = new SignedConfig(0, Arrays.asList(sdk1, sdk2)); + assertThat(config.getMatchingConfig(2)).isEqualTo(sdk2); + } + + @Test + public void testGetMatchingConfigInRange() { + SignedConfig.PerSdkConfig sdk13 = new SignedConfig.PerSdkConfig( + 1, 3, Collections.emptyMap()); + SignedConfig.PerSdkConfig sdk46 = new SignedConfig.PerSdkConfig( + 4, 6, Collections.emptyMap()); + SignedConfig config = new SignedConfig(0, Arrays.asList(sdk13, sdk46)); + assertThat(config.getMatchingConfig(2)).isEqualTo(sdk13); + } + @Test + public void testGetMatchingConfigNoMatch() { + SignedConfig.PerSdkConfig sdk1 = new SignedConfig.PerSdkConfig( + 1, 1, Collections.emptyMap()); + SignedConfig.PerSdkConfig sdk2 = new SignedConfig.PerSdkConfig( + 2, 2, Collections.emptyMap()); + SignedConfig config = new SignedConfig(0, Arrays.asList(sdk1, sdk2)); + assertThat(config.getMatchingConfig(3)).isNull(); + } + +} diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java index bf7f53dd7d8f..860656bf47f4 100644 --- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java +++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java @@ -16,6 +16,8 @@ package com.android.server.usage; +import static android.app.usage.UsageEvents.Event.MAX_EVENT_TYPE; + import static junit.framework.TestCase.fail; import static org.testng.Assert.assertEquals; @@ -136,9 +138,11 @@ public class UsageStatsDatabaseTest { event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP; } - event.mClass = ".fake.class.name" + i % 11; + final int instanceId = i % 11; + event.mClass = ".fake.class.name" + instanceId; event.mTimeStamp = time; - event.mEventType = i % 19; //"random" event type + event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type + event.mInstanceId = instanceId; switch (event.mEventType) { case Event.CONFIGURATION_CHANGE: @@ -160,7 +164,8 @@ public class UsageStatsDatabaseTest { } mIntervalStats.events.insert(event); - mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType); + mIntervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType, + event.mInstanceId); time += timeProgression; // Arbitrary progression of time } @@ -219,7 +224,9 @@ public class UsageStatsDatabaseTest { // mBeginTimeStamp is based on the enclosing IntervalStats, don't bother checking // mEndTimeStamp is based on the enclosing IntervalStats, don't bother checking assertEquals(us1.mLastTimeUsed, us2.mLastTimeUsed); + assertEquals(us1.mLastTimeVisible, us2.mLastTimeVisible); assertEquals(us1.mTotalTimeInForeground, us2.mTotalTimeInForeground); + assertEquals(us1.mTotalTimeVisible, us2.mTotalTimeVisible); assertEquals(us1.mLastTimeForegroundServiceUsed, us2.mLastTimeForegroundServiceUsed); assertEquals(us1.mTotalTimeForegroundServiceUsed, us2.mTotalTimeForegroundServiceUsed); // mLaunchCount not persisted, so skipped diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index ca8cc0d89201..7a5eaa852c34 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -50,9 +50,9 @@ android_test { "liblog", "liblzma", "libnativehelper", - "libnetdaidl", "libui", "libunwindstack", "libutils", + "netd_aidl_interface-cpp", ], } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index d950360791d2..527a1eef7b0f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -20,6 +20,7 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREG import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MAX; @@ -183,7 +184,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Resources mResources; private NotificationChannel mTestNotificationChannel = new NotificationChannel( - TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT); + TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); @Mock private NotificationListeners mListeners; @Mock private NotificationAssistants mAssistants; @@ -238,6 +239,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { protected void reportUserInteraction(NotificationRecord r) { return; } + + @Override + protected void handleSavePolicyFile() { + return; + } } private class TestableToastCallback extends ITransientNotification.Stub { @@ -425,7 +431,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCreateNotificationChannels_SingleChannel() throws Exception { final NotificationChannel channel = - new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); + new NotificationChannel("id", "name", IMPORTANCE_DEFAULT); mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(channel))); final NotificationChannel createdChannel = @@ -447,9 +453,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCreateNotificationChannels_TwoChannels() throws Exception { final NotificationChannel channel1 = - new NotificationChannel("id1", "name", NotificationManager.IMPORTANCE_DEFAULT); + new NotificationChannel("id1", "name", IMPORTANCE_DEFAULT); final NotificationChannel channel2 = - new NotificationChannel("id2", "name", NotificationManager.IMPORTANCE_DEFAULT); + new NotificationChannel("id2", "name", IMPORTANCE_DEFAULT); mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(channel1, channel2))); assertTrue(mBinderService.getNotificationChannel(PKG, "id1") != null); @@ -460,7 +466,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testCreateNotificationChannels_SecondCreateDoesNotChangeImportance() throws Exception { final NotificationChannel channel = - new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); + new NotificationChannel("id", "name", IMPORTANCE_DEFAULT); mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(channel))); @@ -471,14 +477,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { new ParceledListSlice(Arrays.asList(dupeChannel))); final NotificationChannel createdChannel = mBinderService.getNotificationChannel(PKG, "id"); - assertEquals(NotificationManager.IMPORTANCE_DEFAULT, createdChannel.getImportance()); + assertEquals(IMPORTANCE_DEFAULT, createdChannel.getImportance()); } @Test public void testCreateNotificationChannels_SecondCreateAllowedToDowngradeImportance() throws Exception { final NotificationChannel channel = - new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); + new NotificationChannel("id", "name", IMPORTANCE_DEFAULT); mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(channel))); @@ -496,7 +502,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testCreateNotificationChannels_CannotDowngradeImportanceIfAlreadyUpdated() throws Exception { final NotificationChannel channel = - new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); + new NotificationChannel("id", "name", IMPORTANCE_DEFAULT); mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(channel))); @@ -519,14 +525,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testCreateNotificationChannels_IdenticalChannelsInListIgnoresSecond() throws Exception { final NotificationChannel channel1 = - new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); + new NotificationChannel("id", "name", IMPORTANCE_DEFAULT); final NotificationChannel channel2 = new NotificationChannel("id", "name", IMPORTANCE_HIGH); mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(channel1, channel2))); final NotificationChannel createdChannel = mBinderService.getNotificationChannel(PKG, "id"); - assertEquals(NotificationManager.IMPORTANCE_DEFAULT, createdChannel.getImportance()); + assertEquals(IMPORTANCE_DEFAULT, createdChannel.getImportance()); } @Test @@ -751,13 +757,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testBlockedNotifications_blockedByAssistant() throws Exception { when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); NotificationChannel channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_HIGH); NotificationRecord r = generateNotificationRecord(channel); mService.addEnqueuedNotification(r); - r.setAssistantImportance(IMPORTANCE_NONE); + Bundle bundle = new Bundle(); + bundle.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_NONE); + Adjustment adjustment = new Adjustment( + r.sbn.getPackageName(), r.getKey(), bundle, "", r.getUser().getIdentifier()); + mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment); NotificationManagerService.PostNotificationRunnable runnable = mService.new PostNotificationRunnable(r.getKey()); @@ -1809,7 +1820,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mListener = mock(ManagedServices.ManagedServiceInfo.class); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); - try { mBinderService.getNotificationChannelGroupsFromPrivilegedListener( null, PKG, Process.myUserHandle()); @@ -2613,7 +2623,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testAssistantIBlockingTriggersCancel() throws Exception { + public void testAssistantBlockingTriggersCancel() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addNotification(r); NotificationManagerService.WorkerHandler handler = mock( @@ -2653,6 +2663,43 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testApplyEnqueuedAdjustmentFromAssistant_importance_onTime() throws Exception { + final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + mService.addEnqueuedNotification(r); + NotificationManagerService.WorkerHandler handler = mock( + NotificationManagerService.WorkerHandler.class); + mService.setHandler(handler); + when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true); + + Bundle signals = new Bundle(); + signals.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_LOW); + Adjustment adjustment = new Adjustment( + r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment); + + assertEquals(IMPORTANCE_LOW, r.getImportance()); + } + + @Test + public void testApplyEnqueuedAdjustmentFromAssistant_importance_tooLate() throws Exception { + final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(r); + NotificationManagerService.WorkerHandler handler = mock( + NotificationManagerService.WorkerHandler.class); + mService.setHandler(handler); + when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true); + + Bundle signals = new Bundle(); + signals.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_LOW); + Adjustment adjustment = new Adjustment( + r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment); + + assertEquals(IMPORTANCE_DEFAULT, r.getImportance()); + assertFalse(r.hasAdjustment(Adjustment.KEY_IMPORTANCE)); + } + + @Test public void testApplyEnqueuedAdjustmentFromAssistant_crossUser() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addEnqueuedNotification(r); @@ -2852,7 +2899,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void updateUriPermissions_update() throws Exception { NotificationChannel c = new NotificationChannel( - TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT); + TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); Message message1 = new Message("", 0, ""); message1.setData("", diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 8690110ca66a..0eeeeed0b227 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -760,6 +760,7 @@ public class NotificationRecordTest extends UiServiceTestCase { NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); record.setAssistantImportance(IMPORTANCE_LOW); + record.calculateImportance(); assertEquals(IMPORTANCE_LOW, record.getImportance()); // assistant ignored if user expressed preference @@ -767,6 +768,7 @@ public class NotificationRecordTest extends UiServiceTestCase { channel.lockFields(USER_LOCKED_IMPORTANCE); record.setAssistantImportance(IMPORTANCE_LOW); + record.calculateImportance(); assertEquals(channel.getImportance(), record.getImportance()); } @@ -779,6 +781,7 @@ public class NotificationRecordTest extends UiServiceTestCase { NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); record.setAssistantImportance(IMPORTANCE_LOW); + record.calculateImportance(); assertEquals(IMPORTANCE_LOW, record.getImportance()); record.updateNotificationChannel( diff --git a/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java b/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java index 82e0fbe0e400..a71aca53ca26 100644 --- a/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java +++ b/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java @@ -106,8 +106,8 @@ public class PinnedSliceStateTest extends UiServiceTestCase { mPinnedSliceManager.pin("pkg", FIRST_SPECS, mToken); TestableLooper.get(this).processAllMessages(); - verify(mIContentProvider).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null), - argThat(b -> { + verify(mIContentProvider).call(anyString(), anyString(), eq(SliceProvider.METHOD_PIN), + eq(null), argThat(b -> { assertEquals(TEST_URI, b.getParcelable(SliceProvider.EXTRA_BIND_URI)); return true; })); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index 048384e9536e..85410f5e14df 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -82,17 +82,17 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testEmptyTaskCleanupOnRemove() { - assertNotNull(mTask.getWindowContainerController()); + assertNotNull(mTask.getTask()); mStack.removeTask(mTask, "testEmptyTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING); - assertNull(mTask.getWindowContainerController()); + assertNull(mTask.getTask()); } @Test public void testOccupiedTaskCleanupOnRemove() { final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); - assertNotNull(mTask.getWindowContainerController()); + assertNotNull(mTask.getTask()); mStack.removeTask(mTask, "testOccupiedTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING); - assertNotNull(mTask.getWindowContainerController()); + assertNotNull(mTask.getTask()); } @Test @@ -162,7 +162,8 @@ public class ActivityStackTests extends ActivityTestsBase { assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); // Ensure that the override mode is restored to undefined - assertEquals(WINDOWING_MODE_UNDEFINED, primarySplitScreen.getOverrideWindowingMode()); + assertEquals(WINDOWING_MODE_UNDEFINED, + primarySplitScreen.getRequestedOverrideWindowingMode()); } @Test @@ -184,7 +185,8 @@ public class ActivityStackTests extends ActivityTestsBase { assertEquals(0, mDefaultDisplay.getIndexOf(primarySplitScreen)); // Ensure that the override mode is restored to what it was (fullscreen) - assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getOverrideWindowingMode()); + assertEquals(WINDOWING_MODE_FULLSCREEN, + primarySplitScreen.getRequestedOverrideWindowingMode()); } @Test @@ -193,11 +195,13 @@ public class ActivityStackTests extends ActivityTestsBase { WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); - assertEquals(WINDOWING_MODE_UNDEFINED, primarySplitScreen.getOverrideWindowingMode()); + assertEquals(WINDOWING_MODE_UNDEFINED, + primarySplitScreen.getRequestedOverrideWindowingMode()); mDefaultDisplay.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(WINDOWING_MODE_FREEFORM, primarySplitScreen.getWindowingMode()); - assertEquals(WINDOWING_MODE_UNDEFINED, primarySplitScreen.getOverrideWindowingMode()); + assertEquals(WINDOWING_MODE_UNDEFINED, + primarySplitScreen.getRequestedOverrideWindowingMode()); } @Test @@ -206,11 +210,13 @@ public class ActivityStackTests extends ActivityTestsBase { WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); - assertEquals(WINDOWING_MODE_UNDEFINED, primarySplitScreen.getOverrideWindowingMode()); + assertEquals(WINDOWING_MODE_UNDEFINED, + primarySplitScreen.getRequestedOverrideWindowingMode()); primarySplitScreen.setWindowingMode(WINDOWING_MODE_FULLSCREEN); // setting windowing mode should still work even though resolved mode is already fullscreen - assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getOverrideWindowingMode()); + assertEquals(WINDOWING_MODE_FULLSCREEN, + primarySplitScreen.getRequestedOverrideWindowingMode()); mDefaultDisplay.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 5c918b4c8bfe..7c43cf3fba83 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -135,8 +135,8 @@ public class ActivityStarterTests extends ActivityTestsBase { final Rect bounds = new Rect(10, 10, 100, 100); mStarter.updateBounds(task, bounds); - assertEquals(bounds, task.getOverrideBounds()); - assertEquals(new Rect(), task.getStack().getOverrideBounds()); + assertEquals(bounds, task.getRequestedOverrideBounds()); + assertEquals(new Rect(), task.getStack().getRequestedOverrideBounds()); // When in a resizeable stack, the stack bounds should be updated as well. final TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor) @@ -151,10 +151,10 @@ public class ActivityStarterTests extends ActivityTestsBase { // In the case of no animation, the stack and task bounds should be set immediately. if (!ANIMATE) { - assertEquals(bounds, task2.getStack().getOverrideBounds()); - assertEquals(bounds, task2.getOverrideBounds()); + assertEquals(bounds, task2.getStack().getRequestedOverrideBounds()); + assertEquals(bounds, task2.getRequestedOverrideBounds()); } else { - assertEquals(new Rect(), task2.getOverrideBounds()); + assertEquals(new Rect(), task2.getRequestedOverrideBounds()); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 3a56419f67ae..569c6d4af37d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -345,7 +345,7 @@ class ActivityTestsBase { mStack.moveToFront("test"); mStack.addTask(task, true, "creating test task"); task.setStack(mStack); - task.setWindowContainerController(); + task.setTask(); } task.touchActiveTime(); @@ -361,12 +361,12 @@ class ActivityTestsBase { } @Override - void createWindowContainer(boolean onTop, boolean showForAllUsers) { - setWindowContainerController(); + void createTask(boolean onTop, boolean showForAllUsers) { + setTask(); } - private void setWindowContainerController() { - setWindowContainerController(mock(TaskWindowContainerController.class)); + private void setTask() { + setTask(mock(Task.class)); } } } @@ -408,6 +408,7 @@ class ActivityTestsBase { initialize(intentFirewall, intentController, looper); initRootActivityContainerMocks(wm); setWindowManager(wm); + createDefaultDisplay(); } void initRootActivityContainerMocks(WindowManagerService wm) { @@ -424,7 +425,9 @@ class ActivityTestsBase { // Called when moving activity to pinned stack. doNothing().when(mRootActivityContainer).ensureActivitiesVisible(any(), anyInt(), anyBoolean()); + } + void createDefaultDisplay() { // Create a default display and put a home stack on it so that we'll always have // something focusable. mDefaultDisplay = TestActivityDisplay.create(mStackSupervisor, DEFAULT_DISPLAY); @@ -444,7 +447,11 @@ class ActivityTestsBase { } @Override - void updateUsageStats(ActivityRecord component, boolean resumed) { + void updateBatteryStats(ActivityRecord component, boolean resumed) { + } + + @Override + void updateActivityUsageStats(ActivityRecord activity, int event) { } @Override @@ -598,10 +605,8 @@ class ActivityTestsBase { } @Override - protected DisplayWindowController createWindowContainerController() { - DisplayWindowController out = mock(DisplayWindowController.class); - out.mContainer = WindowTestUtils.createTestDisplayContent(); - return out; + protected DisplayContent createDisplayContent() { + return WindowTestUtils.createTestDisplayContent(); } void removeAllTasks() { @@ -616,6 +621,7 @@ class ActivityTestsBase { private static WindowManagerService prepareMockWindowManager() { final WindowManagerService service = mock(WindowManagerService.class); + service.mRoot = mock(RootWindowContainer.class); doAnswer((InvocationOnMock invocationOnMock) -> { final Runnable runnable = invocationOnMock.<Runnable>getArgument(0); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index 577859cf2107..fa4228908cea 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -80,7 +80,7 @@ public class AppTransitionTests extends WindowTestsBase { @Test public void testForceOverride() { mWm.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE, false /* alwaysKeepCurrent */); - mDc.getController().prepareAppTransition(TRANSIT_ACTIVITY_OPEN, + mDc.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */, 0 /* flags */, true /* forceOverride */); assertEquals(TRANSIT_ACTIVITY_OPEN, mDc.mAppTransition.getAppTransition()); } @@ -102,8 +102,8 @@ public class AppTransitionTests extends WindowTestsBase { @Test public void testAppTransitionStateForMultiDisplay() { // Create 2 displays & presume both display the state is ON for ready to display & animate. - final DisplayContent dc1 = createNewDisplayWithController(Display.STATE_ON); - final DisplayContent dc2 = createNewDisplayWithController(Display.STATE_ON); + final DisplayContent dc1 = createNewDisplay(Display.STATE_ON); + final DisplayContent dc2 = createNewDisplay(Display.STATE_ON); // Create 2 app window tokens to represent 2 activity window. final WindowTestUtils.TestAppWindowToken token1 = createTestAppWindowToken(dc1, @@ -117,10 +117,10 @@ public class AppTransitionTests extends WindowTestsBase { // Simulate activity resume / finish flows to prepare app transition & set visibility, // make sure transition is set as expected for each display. - dc1.getController().prepareAppTransition(TRANSIT_ACTIVITY_OPEN, + dc1.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */, 0 /* flags */, false /* forceOverride */); assertEquals(TRANSIT_ACTIVITY_OPEN, dc1.mAppTransition.getAppTransition()); - dc2.getController().prepareAppTransition(TRANSIT_ACTIVITY_CLOSE, + dc2.prepareAppTransition(TRANSIT_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */, 0 /* flags */, false /* forceOverride */); assertEquals(TRANSIT_ACTIVITY_CLOSE, dc2.mAppTransition.getAppTransition()); // One activity window is visible for resuming & the other activity window is invisible @@ -138,8 +138,8 @@ public class AppTransitionTests extends WindowTestsBase { @Test public void testCleanAppTransitionWhenTaskStackReparent() { // Create 2 displays & presume both display the state is ON for ready to display & animate. - final DisplayContent dc1 = createNewDisplayWithController(Display.STATE_ON); - final DisplayContent dc2 = createNewDisplayWithController(Display.STATE_ON); + final DisplayContent dc1 = createNewDisplay(Display.STATE_ON); + final DisplayContent dc2 = createNewDisplay(Display.STATE_ON); final TaskStack stack1 = createTaskStackOnDisplay(dc1); final Task task1 = createTaskInStack(stack1, 0 /* userId */); @@ -151,7 +151,7 @@ public class AppTransitionTests extends WindowTestsBase { dc1.mClosingApps.add(token1); assertTrue(dc1.mClosingApps.size() > 0); - dc1.getController().prepareAppTransition(TRANSIT_ACTIVITY_OPEN, + dc1.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */, 0 /* flags */, false /* forceOverride */); assertEquals(TRANSIT_ACTIVITY_OPEN, dc1.mAppTransition.getAppTransition()); assertTrue(dc1.mAppTransition.isTransitionSet()); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java index 0ee532d0b075..92b4dbb5076b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java @@ -155,15 +155,15 @@ public class AppWindowTokenTests extends WindowTestsBase { // Set initial orientation and update. mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - mWm.updateOrientationFromAppTokens(mDisplayContent.getOverrideConfiguration(), null, - mDisplayContent.getDisplayId()); + mWm.updateOrientationFromAppTokens(mDisplayContent.getRequestedOverrideConfiguration(), + null, mDisplayContent.getDisplayId()); assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation()); appWindow.mResizeReported = false; // Update the orientation to perform 180 degree rotation and check that resize was reported. mToken.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE); - mWm.updateOrientationFromAppTokens(mDisplayContent.getOverrideConfiguration(), null, - mDisplayContent.getDisplayId()); + mWm.updateOrientationFromAppTokens(mDisplayContent.getRequestedOverrideConfiguration(), + null, mDisplayContent.getDisplayId()); mWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, mDisplayContent.getLastOrientation()); assertTrue(appWindow.mResizeReported); diff --git a/services/tests/wmtests/src/com/android/server/wm/ConfigurationContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/ConfigurationContainerTests.java index 82a200bc207f..e7f7d2106e6b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ConfigurationContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ConfigurationContainerTests.java @@ -27,6 +27,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; import static android.content.res.Configuration.EMPTY; +import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -55,22 +56,22 @@ public class ConfigurationContainerTests { public void testConfigurationInit() { // Check root container initial config. final TestConfigurationContainer root = new TestConfigurationContainer(); - assertEquals(EMPTY, root.getOverrideConfiguration()); + assertEquals(EMPTY, root.getRequestedOverrideConfiguration()); assertEquals(EMPTY, root.getMergedOverrideConfiguration()); assertEquals(EMPTY, root.getConfiguration()); // Check child initial config. final TestConfigurationContainer child1 = root.addChild(); - assertEquals(EMPTY, child1.getOverrideConfiguration()); + assertEquals(EMPTY, child1.getRequestedOverrideConfiguration()); assertEquals(EMPTY, child1.getMergedOverrideConfiguration()); assertEquals(EMPTY, child1.getConfiguration()); // Check child initial config if root has overrides. final Configuration rootOverrideConfig = new Configuration(); rootOverrideConfig.fontScale = 1.3f; - root.onOverrideConfigurationChanged(rootOverrideConfig); + root.onRequestedOverrideConfigurationChanged(rootOverrideConfig); final TestConfigurationContainer child2 = root.addChild(); - assertEquals(EMPTY, child2.getOverrideConfiguration()); + assertEquals(EMPTY, child2.getRequestedOverrideConfiguration()); assertEquals(rootOverrideConfig, child2.getMergedOverrideConfiguration()); assertEquals(rootOverrideConfig, child2.getConfiguration()); @@ -83,7 +84,7 @@ public class ConfigurationContainerTests { rootFullConfig.updateFrom(rootOverrideConfig); final TestConfigurationContainer child3 = root.addChild(); - assertEquals(EMPTY, child3.getOverrideConfiguration()); + assertEquals(EMPTY, child3.getRequestedOverrideConfiguration()); assertEquals(rootOverrideConfig, child3.getMergedOverrideConfiguration()); assertEquals(rootFullConfig, child3.getConfiguration()); } @@ -94,24 +95,24 @@ public class ConfigurationContainerTests { final TestConfigurationContainer root = new TestConfigurationContainer(); final Configuration rootOverrideConfig = new Configuration(); rootOverrideConfig.fontScale = 1.3f; - root.onOverrideConfigurationChanged(rootOverrideConfig); + root.onRequestedOverrideConfigurationChanged(rootOverrideConfig); // Init child's config. final TestConfigurationContainer child = root.addChild(); final Configuration childOverrideConfig = new Configuration(); childOverrideConfig.densityDpi = 320; - child.onOverrideConfigurationChanged(childOverrideConfig); + child.onRequestedOverrideConfigurationChanged(childOverrideConfig); final Configuration mergedOverrideConfig = new Configuration(root.getConfiguration()); mergedOverrideConfig.updateFrom(childOverrideConfig); // Check configuration update when child is removed from parent. root.removeChild(child); - assertEquals(childOverrideConfig, child.getOverrideConfiguration()); + assertEquals(childOverrideConfig, child.getRequestedOverrideConfiguration()); assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration()); assertEquals(mergedOverrideConfig, child.getConfiguration()); // It may be paranoia... but let's check if parent's config didn't change after removal. - assertEquals(rootOverrideConfig, root.getOverrideConfiguration()); + assertEquals(rootOverrideConfig, root.getRequestedOverrideConfiguration()); assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration()); assertEquals(rootOverrideConfig, root.getConfiguration()); @@ -119,13 +120,13 @@ public class ConfigurationContainerTests { final TestConfigurationContainer root2 = new TestConfigurationContainer(); final Configuration rootOverrideConfig2 = new Configuration(); rootOverrideConfig2.fontScale = 1.1f; - root2.onOverrideConfigurationChanged(rootOverrideConfig2); + root2.onRequestedOverrideConfigurationChanged(rootOverrideConfig2); // Check configuration update when child is added to different parent. mergedOverrideConfig.setTo(rootOverrideConfig2); mergedOverrideConfig.updateFrom(childOverrideConfig); root2.addChild(child); - assertEquals(childOverrideConfig, child.getOverrideConfiguration()); + assertEquals(childOverrideConfig, child.getRequestedOverrideConfiguration()); assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration()); assertEquals(mergedOverrideConfig, child.getConfiguration()); } @@ -141,24 +142,24 @@ public class ConfigurationContainerTests { final Configuration rootOverrideConfig = new Configuration(); rootOverrideConfig.fontScale = 1.3f; rootOverrideConfig.orientation = SCREEN_ORIENTATION_REVERSE_LANDSCAPE; - root.onOverrideConfigurationChanged(rootOverrideConfig); + root.onRequestedOverrideConfigurationChanged(rootOverrideConfig); // Init children. final TestConfigurationContainer child1 = root.addChild(); final Configuration childOverrideConfig1 = new Configuration(); childOverrideConfig1.densityDpi = 320; childOverrideConfig1.orientation = SCREEN_ORIENTATION_LANDSCAPE; - child1.onOverrideConfigurationChanged(childOverrideConfig1); + child1.onRequestedOverrideConfigurationChanged(childOverrideConfig1); final TestConfigurationContainer child2 = child1.addChild(); final Configuration childOverrideConfig2 = new Configuration(); childOverrideConfig2.screenWidthDp = 150; childOverrideConfig2.orientation = SCREEN_ORIENTATION_PORTRAIT; - child2.onOverrideConfigurationChanged(childOverrideConfig2); + child2.onRequestedOverrideConfigurationChanged(childOverrideConfig2); // Check configuration on all levels when root override is updated. rootOverrideConfig.smallestScreenWidthDp = 200; - root.onOverrideConfigurationChanged(rootOverrideConfig); + root.onRequestedOverrideConfigurationChanged(rootOverrideConfig); final Configuration mergedOverrideConfig1 = new Configuration(rootOverrideConfig); mergedOverrideConfig1.updateFrom(childOverrideConfig1); @@ -168,15 +169,15 @@ public class ConfigurationContainerTests { mergedOverrideConfig2.updateFrom(childOverrideConfig2); final Configuration mergedConfig2 = new Configuration(mergedOverrideConfig2); - assertEquals(rootOverrideConfig, root.getOverrideConfiguration()); + assertEquals(rootOverrideConfig, root.getRequestedOverrideConfiguration()); assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration()); assertEquals(rootOverrideConfig, root.getConfiguration()); - assertEquals(childOverrideConfig1, child1.getOverrideConfiguration()); + assertEquals(childOverrideConfig1, child1.getRequestedOverrideConfiguration()); assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration()); assertEquals(mergedConfig1, child1.getConfiguration()); - assertEquals(childOverrideConfig2, child2.getOverrideConfiguration()); + assertEquals(childOverrideConfig2, child2.getRequestedOverrideConfiguration()); assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration()); assertEquals(mergedConfig2, child2.getConfiguration()); @@ -194,15 +195,15 @@ public class ConfigurationContainerTests { mergedConfig2.setTo(mergedConfig1); mergedConfig2.updateFrom(mergedOverrideConfig2); - assertEquals(rootOverrideConfig, root.getOverrideConfiguration()); + assertEquals(rootOverrideConfig, root.getRequestedOverrideConfiguration()); assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration()); assertEquals(mergedRootConfig, root.getConfiguration()); - assertEquals(childOverrideConfig1, child1.getOverrideConfiguration()); + assertEquals(childOverrideConfig1, child1.getRequestedOverrideConfiguration()); assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration()); assertEquals(mergedConfig1, child1.getConfiguration()); - assertEquals(childOverrideConfig2, child2.getOverrideConfiguration()); + assertEquals(childOverrideConfig2, child2.getRequestedOverrideConfiguration()); assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration()); assertEquals(mergedConfig2, child2.getConfiguration()); } @@ -274,15 +275,55 @@ public class ConfigurationContainerTests { final Configuration config = new Configuration(); config.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); config.windowConfiguration.setAppBounds(10, 10, 10, 10); - container.onOverrideConfigurationChanged(config); + container.onRequestedOverrideConfigurationChanged(config); container.registerConfigurationChangeListener(listener); // Assert listener got the current config. of the container after it was registered. assertEquals(config, listener.mOverrideConfiguration); // Assert listener gets changes to override configuration. - container.onOverrideConfigurationChanged(EMPTY); + container.onRequestedOverrideConfigurationChanged(EMPTY); assertEquals(EMPTY, listener.mOverrideConfiguration); } + @Test + public void testConfigurationConstraints() { + // Init root config. + final TestConfigurationContainer root = new TestConfigurationContainer(); + final Configuration rootOverrideConfig = new Configuration(); + rootOverrideConfig.smallestScreenWidthDp = 140; + root.onRequestedOverrideConfigurationChanged(rootOverrideConfig); + + // Init child with constraint + final TestConfigurationChangeListener listener = new TestConfigurationChangeListener(); + final TestConfigurationContainer child1 = root.addConstraintChild(); + child1.registerConfigurationChangeListener(listener); + final Configuration childOverrideConfig1 = new Configuration(); + childOverrideConfig1.orientation = SCREEN_ORIENTATION_LANDSCAPE; + child1.onRequestedOverrideConfigurationChanged(childOverrideConfig1); + + assertEquals(SMALLEST_SCREEN_WIDTH_DP_UNDEFINED, + child1.getRequestedOverrideConfiguration().smallestScreenWidthDp); + assertEquals(100, child1.getConfiguration().smallestScreenWidthDp); + assertEquals(100, listener.mOverrideConfiguration.smallestScreenWidthDp); + + // Check configuration on all levels when root override is updated. + rootOverrideConfig.smallestScreenWidthDp = 80; + root.onRequestedOverrideConfigurationChanged(rootOverrideConfig); + + assertEquals(SMALLEST_SCREEN_WIDTH_DP_UNDEFINED, + child1.getRequestedOverrideConfiguration().smallestScreenWidthDp); + assertEquals(80, child1.getConfiguration().smallestScreenWidthDp); + assertEquals(80, listener.mOverrideConfiguration.smallestScreenWidthDp); + + rootOverrideConfig.smallestScreenWidthDp = 180; + root.onRequestedOverrideConfigurationChanged(rootOverrideConfig); + + assertEquals(SMALLEST_SCREEN_WIDTH_DP_UNDEFINED, + child1.getRequestedOverrideConfiguration().smallestScreenWidthDp); + assertEquals(100, child1.getConfiguration().smallestScreenWidthDp); + assertEquals(100, child1.getMergedOverrideConfiguration().smallestScreenWidthDp); + assertEquals(100, listener.mOverrideConfiguration.smallestScreenWidthDp); + } + /** * Contains minimal implementation of {@link ConfigurationContainer}'s abstract behavior needed * for testing. @@ -303,6 +344,10 @@ public class ConfigurationContainerTests { return addChild(new TestConfigurationContainer()); } + TestConfigurationContainer addConstraintChild() { + return addChild(new TestConfigurationContainerWithConstraints()); + } + void removeChild(TestConfigurationContainer child) { child.mParent = null; child.onParentChanged(); @@ -324,12 +369,35 @@ public class ConfigurationContainerTests { } } + /** + * Contains minimal implementation of {@link ConfigurationContainer}'s abstract behavior needed + * for testing. + */ + private class TestConfigurationContainerWithConstraints + extends TestConfigurationContainer { + + @Override + public void resolveOverrideConfiguration(Configuration newParentConfig) { + // Restrict smallestScreenWidthDp to 100 + getResolvedOverrideConfiguration().setTo(getRequestedOverrideConfiguration()); + int smallestScreenWidthDp = + getResolvedOverrideConfiguration().smallestScreenWidthDp + == SMALLEST_SCREEN_WIDTH_DP_UNDEFINED + ? newParentConfig.smallestScreenWidthDp + : getResolvedOverrideConfiguration().smallestScreenWidthDp; + if (smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { + getResolvedOverrideConfiguration().smallestScreenWidthDp = + Math.min(smallestScreenWidthDp, 100); + } + } + } + private static class TestConfigurationChangeListener implements ConfigurationContainerListener { final Configuration mOverrideConfiguration = new Configuration(); @Override - public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { + public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) { mOverrideConfiguration.setTo(overrideConfiguration); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 3b8d71dd8176..7c83ecc22c90 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -227,7 +227,8 @@ public class DisplayContentTests extends WindowTestsBase { */ @Test public void testDisplayOverrideConfigUpdate() { - final Configuration currentOverrideConfig = mDisplayContent.getOverrideConfiguration(); + final Configuration currentOverrideConfig = + mDisplayContent.getRequestedOverrideConfiguration(); // Create new, slightly changed override configuration and apply it to the display. final Configuration newOverrideConfig = new Configuration(currentOverrideConfig); @@ -237,7 +238,7 @@ public class DisplayContentTests extends WindowTestsBase { mWm.setNewDisplayOverrideConfiguration(newOverrideConfig, mDisplayContent); // Check that override config is applied. - assertEquals(newOverrideConfig, mDisplayContent.getOverrideConfiguration()); + assertEquals(newOverrideConfig, mDisplayContent.getRequestedOverrideConfiguration()); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 3c7b4b1248b5..3e025f6f36b5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -875,8 +875,8 @@ public class RecentTasksTest extends ActivityTestsBase { } @Override - void initRootActivityContainerMocks(WindowManagerService wm) { - super.initRootActivityContainerMocks(wm); + void createDefaultDisplay() { + super.createDefaultDisplay(); mDisplay = mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY); mOtherDisplay = TestActivityDisplay.create(mTestStackSupervisor, DEFAULT_DISPLAY + 1); mRootActivityContainer.addChild(mOtherDisplay, ActivityDisplay.POSITION_TOP); diff --git a/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java index ce5b13cab1a7..5690b58737ab 100644 --- a/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java @@ -41,17 +41,14 @@ public class StackWindowControllerTests extends WindowTestsBase { public void testRemoveContainer() { final StackWindowController stackController = createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stackController); + final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController); final TaskStack stack = stackController.mContainer; - final Task task = taskController.mContainer; assertNotNull(stack); assertNotNull(task); stackController.removeContainer(); // Assert that the container was removed. assertNull(stackController.mContainer); - assertNull(taskController.mContainer); assertNull(stack.getDisplayContent()); assertNull(task.getDisplayContent()); assertNull(task.mStack); @@ -61,11 +58,9 @@ public class StackWindowControllerTests extends WindowTestsBase { public void testRemoveContainer_deferRemoval() { final StackWindowController stackController = createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stackController); + final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController); final TaskStack stack = stackController.mContainer; - final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer; // Stack removal is deferred if one of its child is animating. task.setLocalIsAnimating(true); @@ -75,11 +70,12 @@ public class StackWindowControllerTests extends WindowTestsBase { // the stack window container is removed. assertNull(stackController.mContainer); assertNull(stack.getController()); - assertNotNull(taskController.mContainer); - assertNotNull(task.getController()); + assertNotNull(task); stack.removeImmediately(); - assertNull(taskController.mContainer); + // After removing, the task will be isolated. + assertNull(task.getParent()); + assertEquals(task.getChildCount(), 0); assertNull(task.getController()); } @@ -89,9 +85,7 @@ public class StackWindowControllerTests extends WindowTestsBase { final StackWindowController stack1Controller = createStackControllerOnDisplay(mDisplayContent); final TaskStack stack1 = stack1Controller.mContainer; - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stack1Controller); - final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer; + final WindowTestUtils.TestTask task1 = WindowTestUtils.createTestTask(stack1Controller); task1.mOnDisplayChangedCalled = false; // Create second display and put second stack on it. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index 105f8260022f..fe632d4ab01e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -949,10 +949,11 @@ public class TaskLaunchParamsModifierTests extends ActivityTestsBase { @Test public void testAdjustBoundsToFitNewDisplay_LargerThanDisplay_RTL() { - final Configuration overrideConfig = mRootActivityContainer.getOverrideConfiguration(); + final Configuration overrideConfig = + mRootActivityContainer.getRequestedOverrideConfiguration(); // Egyptian Arabic is a RTL language. overrideConfig.setLayoutDirection(new Locale("ar", "EG")); - mRootActivityContainer.onOverrideConfigurationChanged(overrideConfig); + mRootActivityContainer.onRequestedOverrideConfigurationChanged(overrideConfig); final TestActivityDisplay freeformDisplay = createNewActivityDisplay( WINDOWING_MODE_FREEFORM); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java new file mode 100644 index 000000000000..3e32ed37a6f3 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** + * Test class for {@link Task}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:TaskTests + */ +@SmallTest +@Presubmit +public class TaskTests extends WindowTestsBase { + + @Test + public void testRemoveContainer() { + final StackWindowController stackController1 = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1); + final WindowTestUtils.TestAppWindowToken appToken = + WindowTestUtils.createAppWindowTokenInTask(mDisplayContent, task); + + task.removeIfPossible(); + // Assert that the container was removed. + assertNull(task.getParent()); + assertEquals(task.getChildCount(), 0); + assertNull(appToken.getParent()); + } + + @Test + public void testRemoveContainer_deferRemoval() { + final StackWindowController stackController1 = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1); + final WindowTestUtils.TestAppWindowToken appToken = + WindowTestUtils.createAppWindowTokenInTask(mDisplayContent, task); + + task.mShouldDeferRemoval = true; + + task.removeIfPossible(); + // For the case of deferred removal the task will still be connected to the its app token + // until the task window container is removed. + assertNotNull(task.getParent()); + assertNotEquals(task.getChildCount(), 0); + assertNotNull(appToken.getParent()); + + task.removeImmediately(); + assertNull(task.getParent()); + assertEquals(task.getChildCount(), 0); + assertNull(appToken.getParent()); + } + + @Test + public void testReparent() { + final StackWindowController stackController1 = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1); + final StackWindowController stackController2 = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTask task2 = WindowTestUtils.createTestTask(stackController2); + + boolean gotException = false; + try { + task.reparent(stackController1, 0, false/* moveParents */); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue("Should not be able to reparent to the same parent", gotException); + + final StackWindowController stackController3 = + createStackControllerOnDisplay(mDisplayContent); + stackController3.setContainer(null); + gotException = false; + try { + task.reparent(stackController3, 0, false/* moveParents */); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue("Should not be able to reparent to a stack that doesn't have a container", + gotException); + + task.reparent(stackController2, 0, false/* moveParents */); + assertEquals(stackController2.mContainer, task.getParent()); + assertEquals(0, task.positionInParent()); + assertEquals(1, task2.positionInParent()); + } + + @Test + public void testReparent_BetweenDisplays() { + // Create first stack on primary display. + final StackWindowController stack1Controller = + createStackControllerOnDisplay(mDisplayContent); + final TaskStack stack1 = stack1Controller.mContainer; + final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stack1Controller); + task.mOnDisplayChangedCalled = false; + assertEquals(mDisplayContent, stack1.getDisplayContent()); + + // Create second display and put second stack on it. + final DisplayContent dc = createNewDisplay(); + final StackWindowController stack2Controller = createStackControllerOnDisplay(dc); + final TaskStack stack2 = stack2Controller.mContainer; + final WindowTestUtils.TestTask task2 = WindowTestUtils.createTestTask(stack2Controller); + // Reparent and check state + task.reparent(stack2Controller, 0, false /* moveParents */); + assertEquals(stack2, task.getParent()); + assertEquals(0, task.positionInParent()); + assertEquals(1, task2.positionInParent()); + assertTrue(task.mOnDisplayChangedCalled); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java deleted file mode 100644 index bbf508dd1630..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; - -import org.junit.Test; - -/** - * Test class for {@link TaskWindowContainerController}. - * - * Build/Install/Run: - * atest FrameworksServicesTests:TaskWindowContainerControllerTests - */ -@SmallTest -@Presubmit -public class TaskWindowContainerControllerTests extends WindowTestsBase { - - /* Comment out due to removal of AppWindowContainerController - @Test - public void testRemoveContainer() { - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(this); - final WindowTestUtils.TestAppWindowContainerController appController = - new WindowTestUtils.TestAppWindowContainerController(taskController); - - taskController.removeContainer(); - // Assert that the container was removed. - assertNull(taskController.mContainer); - assertNull(appController.mContainer); - } - */ - - /* Comment out due to removal of AppWindowContainerController - @Test - public void testRemoveContainer_deferRemoval() { - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(this); - final WindowTestUtils.TestAppWindowContainerController appController = - new WindowTestUtils.TestAppWindowContainerController(taskController); - - final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer; - final AppWindowToken app = appController.mContainer; - task.mShouldDeferRemoval = true; - - taskController.removeContainer(); - // For the case of deferred removal the task controller will no longer be connected to the - // container, but the app controller will still be connected to the its container until - // the task window container is removed. - assertNull(taskController.mContainer); - assertNull(task.getController()); - assertNotNull(appController.mContainer); - assertNotNull(app.getController()); - - task.removeImmediately(); - assertNull(appController.mContainer); - assertNull(app.getController()); - } - */ - - @Test - public void testReparent() { - final StackWindowController stackController1 = - createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stackController1); - final StackWindowController stackController2 = - createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController2 = - new WindowTestUtils.TestTaskWindowContainerController(stackController2); - - boolean gotException = false; - try { - taskController.reparent(stackController1, 0, false/* moveParents */); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue("Should not be able to reparent to the same parent", gotException); - - final StackWindowController stackController3 = - createStackControllerOnDisplay(mDisplayContent); - stackController3.setContainer(null); - gotException = false; - try { - taskController.reparent(stackController3, 0, false/* moveParents */); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue("Should not be able to reparent to a stack that doesn't have a container", - gotException); - - taskController.reparent(stackController2, 0, false/* moveParents */); - assertEquals(stackController2.mContainer, taskController.mContainer.getParent()); - assertEquals(0, ((WindowTestUtils.TestTask) taskController.mContainer).positionInParent()); - assertEquals(1, ((WindowTestUtils.TestTask) taskController2.mContainer).positionInParent()); - } - - @Test - public void testReparent_BetweenDisplays() { - // Create first stack on primary display. - final StackWindowController stack1Controller = - createStackControllerOnDisplay(mDisplayContent); - final TaskStack stack1 = stack1Controller.mContainer; - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stack1Controller); - final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer; - task1.mOnDisplayChangedCalled = false; - assertEquals(mDisplayContent, stack1.getDisplayContent()); - - // Create second display and put second stack on it. - final DisplayContent dc = createNewDisplay(); - final StackWindowController stack2Controller = createStackControllerOnDisplay(dc); - final TaskStack stack2 = stack2Controller.mContainer; - final WindowTestUtils.TestTaskWindowContainerController taskController2 = - new WindowTestUtils.TestTaskWindowContainerController(stack2Controller); - final WindowTestUtils.TestTask task2 = - (WindowTestUtils.TestTask) taskController2.mContainer; - - // Reparent and check state - taskController.reparent(stack2Controller, 0, false /* moveParents */); - assertEquals(stack2, task1.getParent()); - assertEquals(0, task1.positionInParent()); - assertEquals(1, task2.positionInParent()); - assertTrue(task1.mOnDisplayChangedCalled); - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java index 612f9ad923d6..8854edef9994 100644 --- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java @@ -49,7 +49,7 @@ public class UnknownAppVisibilityControllerTest extends WindowTestsBase { mDisplayContent.mUnknownAppVisibilityController.notifyRelayouted(token); // Make sure our handler processed the message. - SystemClock.sleep(100); + mWm.mH.runWithScissors(() -> { }, 0); assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved()); } @@ -65,7 +65,7 @@ public class UnknownAppVisibilityControllerTest extends WindowTestsBase { mDisplayContent.mUnknownAppVisibilityController.notifyRelayouted(token2); // Make sure our handler processed the message. - SystemClock.sleep(100); + mWm.mH.runWithScissors(() -> { }, 0); assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerControllerTests.java index 7592f1c1fca0..de3567ed5018 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerControllerTests.java @@ -93,19 +93,19 @@ public class WindowContainerControllerTests extends WindowTestsBase { controller.setContainer(container); assertEquals(controller.mContainer, container); - assertEquals(EMPTY, container.getOverrideConfiguration()); + assertEquals(EMPTY, container.getRequestedOverrideConfiguration()); final Configuration config = new Configuration(); config.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); config.windowConfiguration.setAppBounds(10, 10, 10, 10); // Assert that the config change through the controller is propagated to the container. - controller.onOverrideConfigurationChanged(config); - assertEquals(config, container.getOverrideConfiguration()); + controller.onRequestedOverrideConfigurationChanged(config); + assertEquals(config, container.getRequestedOverrideConfiguration()); // Assert the container configuration isn't changed after removal from the controller. controller.removeContainer(); - controller.onOverrideConfigurationChanged(EMPTY); - assertEquals(config, container.getOverrideConfiguration()); + controller.onRequestedOverrideConfigurationChanged(EMPTY); + assertEquals(config, container.getRequestedOverrideConfiguration()); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index d4a32cfc4a1d..60f957f8f569 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -424,7 +424,7 @@ public class WindowContainerTests extends WindowTestsBase { final TestWindowContainer parent = grandparent.addChildWindow(); final TestWindowContainer child = parent.addChildWindow(); - child.onOverrideConfigurationChanged(new Configuration()); + child.onRequestedOverrideConfigurationChanged(new Configuration()); assertTrue(grandparent.mOnDescendantOverrideCalled); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java index 60a8aeb5fcc6..d9ef10d7eecb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java @@ -96,7 +96,7 @@ public class WindowFrameTests extends WindowTestsBase { } @Override - public void getOverrideBounds(Rect outBounds) { + public void getRequestedOverrideBounds(Rect outBounds) { outBounds.set(mBounds); } @Override @@ -448,9 +448,9 @@ public class WindowFrameTests extends WindowTestsBase { final int xInset = logicalWidth / 10; final int yInset = logicalWidth / 10; final Rect cf = new Rect(xInset, yInset, logicalWidth - xInset, logicalHeight - yInset); - Configuration config = new Configuration(w.mAppToken.getOverrideConfiguration()); + Configuration config = new Configuration(w.mAppToken.getRequestedOverrideConfiguration()); config.windowConfiguration.setBounds(cf); - w.mAppToken.onOverrideConfigurationChanged(config); + w.mAppToken.onRequestedOverrideConfigurationChanged(config); pf.set(0, 0, logicalWidth, logicalHeight); task.mFullscreenForTest = true; windowFrames.setFrames(pf, pf, pf, cf, cf, pf, cf, mEmptyRect); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRule.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRule.java index 50fd188cc00b..522ab9ffb291 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRule.java @@ -149,10 +149,9 @@ public class WindowManagerServiceRule implements TestRule { mService.onInitReady(); final Display display = mService.mDisplayManager.getDisplay(DEFAULT_DISPLAY); - final DisplayWindowController dcw = new DisplayWindowController(display, mService); // Display creation is driven by the ActivityManagerService via // ActivityStackSupervisor. We emulate those steps here. - mService.mRoot.createDisplayContent(display, dcw); + mService.mRoot.createDisplayContent(display, mock(ActivityDisplay.class)); } private void removeServices() { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java index aa0ecf8447f3..3170f5adf6c3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java @@ -58,8 +58,8 @@ public class WindowTestUtils { public static class TestDisplayContent extends DisplayContent { private TestDisplayContent(Display display, WindowManagerService service, - DisplayWindowController controller) { - super(display, service, controller); + ActivityDisplay activityDisplay) { + super(display, service, activityDisplay); } /** @@ -141,6 +141,13 @@ public class WindowTestUtils { } } + /** Creates an {@link AppWindowToken} and adds it to the specified {@link Task}. */ + public static TestAppWindowToken createAppWindowTokenInTask(DisplayContent dc, Task task) { + final TestAppWindowToken newToken = createTestAppWindowToken(dc); + task.addChild(newToken, POSITION_TOP); + return newToken; + } + /** * An extension of {@link TestTaskStack}, which overrides package scoped methods that would not * normally be mocked out. @@ -264,9 +271,10 @@ public class WindowTestUtils { TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode, boolean supportsPictureInPicture, - TaskWindowContainerController controller) { + TaskRecord taskRecord) { super(taskId, stack, userId, service, resizeMode, supportsPictureInPicture, - new ActivityManager.TaskDescription(), controller); + new ActivityManager.TaskDescription(), taskRecord); + stack.addTask(this, POSITION_TOP); } boolean shouldDeferRemoval() { @@ -293,49 +301,10 @@ public class WindowTestUtils { } } - /** - * Used so we can gain access to some protected members of {@link TaskWindowContainerController} - * class. - */ - public static class TestTaskWindowContainerController extends TaskWindowContainerController { - - static final TaskWindowContainerListener NOP_LISTENER = new TaskWindowContainerListener() { - @Override - public void registerConfigurationChangeListener( - ConfigurationContainerListener listener) { - } - - @Override - public void unregisterConfigurationChangeListener( - ConfigurationContainerListener listener) { - } - - @Override - public void onSnapshotChanged(ActivityManager.TaskSnapshot snapshot) { - } - - @Override - public void requestResize(Rect bounds, int resizeMode) { - } - }; - - TestTaskWindowContainerController(WindowTestsBase testsBase) { - this(testsBase.createStackControllerOnDisplay(testsBase.mDisplayContent)); - } - - TestTaskWindowContainerController(StackWindowController stackController) { - super(sNextTaskId++, NOP_LISTENER, stackController, 0 /* userId */, null /* bounds */, - RESIZE_MODE_UNRESIZEABLE, false /* supportsPictureInPicture */, true /* toTop*/, - true /* showForAllUsers */, new ActivityManager.TaskDescription(), - stackController.mService); - } - - @Override - TestTask createTask(int taskId, TaskStack stack, int userId, int resizeMode, - boolean supportsPictureInPicture, ActivityManager.TaskDescription taskDescription) { - return new TestTask(taskId, stack, userId, mService, resizeMode, - supportsPictureInPicture, this); - } + public static TestTask createTestTask(StackWindowController stackWindowController) { + return new TestTask(sNextTaskId++, stackWindowController.mContainer, 0, + stackWindowController.mService, RESIZE_MODE_UNRESIZEABLE, false, + mock(TaskRecord.class)); } public static class TestIApplicationToken implements IApplicationToken { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index b3f56e7ae99f..5c3368bd25ac 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -339,7 +339,7 @@ class WindowTestsBase { final int stackId = ++sNextStackId; final StackWindowController controller = new StackWindowController(stackId, null, dc.getDisplayId(), true /* onTop */, new Rect(), mWm); - controller.onOverrideConfigurationChanged(overrideConfig); + controller.onRequestedOverrideConfigurationChanged(overrideConfig); return controller; } } @@ -360,21 +360,17 @@ class WindowTestsBase { final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); synchronized (mWm.mGlobalLock) { - return new DisplayContent(display, mWm, mock(DisplayWindowController.class)); + return new DisplayContent(display, mWm, mock(ActivityDisplay.class)); } } /** * Creates a {@link DisplayContent} with given display state and adds it to the system. * - * Unlike {@link #createNewDisplay()} that uses a mock {@link DisplayWindowController} to - * initialize {@link DisplayContent}, this method used real controller object when the test - * need to verify its related flows. - * * @param displayState For initializing the state of the display. See * {@link Display#getState()}. */ - DisplayContent createNewDisplayWithController(int displayState) { + DisplayContent createNewDisplay(int displayState) { // Leverage main display info & initialize it with display state for given displayId. DisplayInfo displayInfo = new DisplayInfo(); displayInfo.copyFrom(mDisplayInfo); @@ -382,11 +378,11 @@ class WindowTestsBase { final int displayId = sNextDisplayId++; final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); - final DisplayWindowController dcw = new DisplayWindowController(display, mWm); synchronized (mWm.mGlobalLock) { // Display creation is driven by DisplayWindowController via ActivityStackSupervisor. // We skip those steps here. - return mWm.mRoot.createDisplayContent(display, dcw); + final ActivityDisplay mockAd = mock(ActivityDisplay.class); + return mWm.mRoot.createDisplayContent(display, mockAd); } } diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java index 4f573a475ae7..65ed85db17bd 100644 --- a/services/usage/java/com/android/server/usage/AppStandbyController.java +++ b/services/usage/java/com/android/server/usage/AppStandbyController.java @@ -842,8 +842,8 @@ public class AppStandbyController { final boolean previouslyIdle = mAppIdleHistory.isIdle( event.mPackage, userId, elapsedRealtime); // Inform listeners if necessary - if ((event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND - || event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND + if ((event.mEventType == UsageEvents.Event.ACTIVITY_RESUMED + || event.mEventType == UsageEvents.Event.ACTIVITY_PAUSED || event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION || event.mEventType == UsageEvents.Event.USER_INTERACTION || event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN @@ -894,8 +894,8 @@ public class AppStandbyController { private int usageEventToSubReason(int eventType) { switch (eventType) { - case UsageEvents.Event.MOVE_TO_FOREGROUND: return REASON_SUB_USAGE_MOVE_TO_FOREGROUND; - case UsageEvents.Event.MOVE_TO_BACKGROUND: return REASON_SUB_USAGE_MOVE_TO_BACKGROUND; + case UsageEvents.Event.ACTIVITY_RESUMED: return REASON_SUB_USAGE_MOVE_TO_FOREGROUND; + case UsageEvents.Event.ACTIVITY_PAUSED: return REASON_SUB_USAGE_MOVE_TO_BACKGROUND; case UsageEvents.Event.SYSTEM_INTERACTION: return REASON_SUB_USAGE_SYSTEM_INTERACTION; case UsageEvents.Event.USER_INTERACTION: return REASON_SUB_USAGE_USER_INTERACTION; case UsageEvents.Event.NOTIFICATION_SEEN: return REASON_SUB_USAGE_NOTIFICATION_SEEN; diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index 84052672f6d3..94cc6502a12d 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -15,10 +15,31 @@ */ package com.android.server.usage; +import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED; +import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED; +import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE; +import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY; +import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE; +import static android.app.usage.UsageEvents.Event.END_OF_DAY; +import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK; +import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START; +import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP; +import static android.app.usage.UsageEvents.Event.KEYGUARD_HIDDEN; +import static android.app.usage.UsageEvents.Event.KEYGUARD_SHOWN; +import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION; +import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE; +import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE; +import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE; +import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; +import static android.app.usage.UsageEvents.Event.STANDBY_BUCKET_CHANGED; +import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION; + import android.app.usage.ConfigurationStats; import android.app.usage.EventList; import android.app.usage.EventStats; -import android.app.usage.UsageEvents; +import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; import android.content.res.Configuration; import android.util.ArrayMap; @@ -125,8 +146,8 @@ public class IntervalStats { /** * Builds a UsageEvents.Event, but does not add it internally. */ - UsageEvents.Event buildEvent(String packageName, String className) { - UsageEvents.Event event = new UsageEvents.Event(); + Event buildEvent(String packageName, String className) { + Event event = new Event(); event.mPackage = getCachedStringRef(packageName); if (className != null) { event.mClass = getCachedStringRef(className); @@ -138,9 +159,9 @@ public class IntervalStats { * Builds a UsageEvents.Event from a proto, but does not add it internally. * Built here to take advantage of the cached String Refs */ - UsageEvents.Event buildEvent(ProtoInputStream parser, List<String> stringPool) + Event buildEvent(ProtoInputStream parser, List<String> stringPool) throws IOException { - final UsageEvents.Event event = new UsageEvents.Event(); + final Event event = new Event(); while (true) { switch (parser.nextField()) { case (int) IntervalStatsProto.Event.PACKAGE: @@ -190,20 +211,23 @@ public class IntervalStats { parser.readInt(IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX) - 1)); break; + case (int) IntervalStatsProto.Event.INSTANCE_ID: + event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID); + break; case ProtoInputStream.NO_MORE_FIELDS: // Handle default values for certain events types switch (event.mEventType) { - case UsageEvents.Event.CONFIGURATION_CHANGE: + case CONFIGURATION_CHANGE: if (event.mConfiguration == null) { event.mConfiguration = new Configuration(); } break; - case UsageEvents.Event.SHORTCUT_INVOCATION: + case SHORTCUT_INVOCATION: if (event.mShortcutId == null) { event.mShortcutId = ""; } break; - case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + case NOTIFICATION_INTERRUPTION: if (event.mNotificationChannelId == null) { event.mNotificationChannelId = ""; } @@ -220,14 +244,15 @@ public class IntervalStats { private boolean isStatefulEvent(int eventType) { switch (eventType) { - case UsageEvents.Event.MOVE_TO_FOREGROUND: - case UsageEvents.Event.MOVE_TO_BACKGROUND: - case UsageEvents.Event.FOREGROUND_SERVICE_START: - case UsageEvents.Event.FOREGROUND_SERVICE_STOP: - case UsageEvents.Event.END_OF_DAY: - case UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE: - case UsageEvents.Event.CONTINUE_PREVIOUS_DAY: - case UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE: + case ACTIVITY_RESUMED: + case ACTIVITY_PAUSED: + case ACTIVITY_STOPPED: + case FOREGROUND_SERVICE_START: + case FOREGROUND_SERVICE_STOP: + case END_OF_DAY: + case ROLLOVER_FOREGROUND_SERVICE: + case CONTINUE_PREVIOUS_DAY: + case CONTINUING_FOREGROUND_SERVICE: return true; } return false; @@ -238,17 +263,56 @@ public class IntervalStats { * interaction. Excludes those that are internally generated. */ private boolean isUserVisibleEvent(int eventType) { - return eventType != UsageEvents.Event.SYSTEM_INTERACTION - && eventType != UsageEvents.Event.STANDBY_BUCKET_CHANGED; + return eventType != SYSTEM_INTERACTION + && eventType != STANDBY_BUCKET_CHANGED; } /** + * Update the IntervalStats by a activity or foreground service event. + * @param packageName package name of this event. Is null if event targets to all packages. + * @param className class name of a activity or foreground service, could be null to if this + * is sent to all activities/services in this package. + * @param timeStamp Epoch timestamp in milliseconds. + * @param eventType event type as in {@link Event} + * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken. + * if className is not an activity, instanceId is not used. * @hide */ @VisibleForTesting - public void update(String packageName, String className, long timeStamp, int eventType) { - UsageStats usageStats = getOrCreateUsageStats(packageName); - usageStats.update(className, timeStamp, eventType); + public void update(String packageName, String className, long timeStamp, int eventType, + int instanceId) { + if (eventType == FLUSH_TO_DISK) { + // FLUSH_TO_DISK are sent to all packages. + final int size = packageStats.size(); + for (int i = 0; i < size; i++) { + UsageStats usageStats = packageStats.valueAt(i); + usageStats.update(null, timeStamp, eventType, instanceId); + } + } else if (eventType == ACTIVITY_DESTROYED) { + UsageStats usageStats = packageStats.get(packageName); + if (usageStats != null) { + // If previous event is not ACTIVITY_STOPPED, convert ACTIVITY_DESTROYED + // to ACTIVITY_STOPPED and add to event list. + // Otherwise do not add anything to event list. (Because we want to save space + // and we do not want a ACTIVITY_STOPPED followed by + // ACTIVITY_DESTROYED in event list). + final int index = usageStats.mActivities.indexOfKey(instanceId); + if (index >= 0) { + final int type = usageStats.mActivities.valueAt(index); + if (type != ACTIVITY_STOPPED) { + Event event = new Event(ACTIVITY_STOPPED, timeStamp); + event.mPackage = packageName; + event.mClass = className; + event.mInstanceId = instanceId; + addEvent(event); + } + } + usageStats.update(className, timeStamp, ACTIVITY_DESTROYED, instanceId); + } + } else { + UsageStats usageStats = getOrCreateUsageStats(packageName); + usageStats.update(className, timeStamp, eventType, instanceId); + } endTime = timeStamp; } @@ -256,7 +320,7 @@ public class IntervalStats { * @hide */ @VisibleForTesting - public void addEvent(UsageEvents.Event event) { + public void addEvent(Event event) { if (events == null) { events = new EventList(); } @@ -265,7 +329,7 @@ public class IntervalStats { if (event.mClass != null) { event.mClass = getCachedStringRef(event.mClass); } - if (event.mEventType == UsageEvents.Event.NOTIFICATION_INTERRUPTION) { + if (event.mEventType == NOTIFICATION_INTERRUPTION) { event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId); } events.insert(event); @@ -338,13 +402,13 @@ public class IntervalStats { } void addEventStatsTo(List<EventStats> out) { - interactiveTracker.addToEventStats(out, UsageEvents.Event.SCREEN_INTERACTIVE, + interactiveTracker.addToEventStats(out, SCREEN_INTERACTIVE, beginTime, endTime); - nonInteractiveTracker.addToEventStats(out, UsageEvents.Event.SCREEN_NON_INTERACTIVE, + nonInteractiveTracker.addToEventStats(out, SCREEN_NON_INTERACTIVE, beginTime, endTime); - keyguardShownTracker.addToEventStats(out, UsageEvents.Event.KEYGUARD_SHOWN, + keyguardShownTracker.addToEventStats(out, KEYGUARD_SHOWN, beginTime, endTime); - keyguardHiddenTracker.addToEventStats(out, UsageEvents.Event.KEYGUARD_HIDDEN, + keyguardHiddenTracker.addToEventStats(out, KEYGUARD_HIDDEN, beginTime, endTime); } diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java index 8e1c06091605..d70653781eff 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsProto.java +++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java @@ -135,6 +135,15 @@ final class UsageStatsProto { stats.mTotalTimeForegroundServiceUsed = proto.readLong( IntervalStatsProto.UsageStats.TOTAL_TIME_SERVICE_USED_MS); break; + case (int) IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS: + // Time attributes stored is an offset of the beginTime. + stats.mLastTimeVisible = statsOut.beginTime + proto.readLong( + IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS); + break; + case (int) IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS: + stats.mTotalTimeVisible = proto.readLong( + IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS); + break; } } if (stats.mLastTimeUsed == 0) { @@ -337,6 +346,11 @@ final class UsageStatsProto { usageStats.mLastTimeForegroundServiceUsed - stats.beginTime); proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_SERVICE_USED_MS, usageStats.mTotalTimeForegroundServiceUsed); + // Time attributes stored as an offset of the beginTime. + proto.write(IntervalStatsProto.UsageStats.LAST_TIME_VISIBLE_MS, + usageStats.mLastTimeVisible - stats.beginTime); + proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS, + usageStats.mTotalTimeVisible); proto.write(IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT, usageStats.mAppLaunchCount); writeChooserCounts(proto, usageStats); proto.end(token); @@ -427,6 +441,7 @@ final class UsageStatsProto { proto.write(IntervalStatsProto.Event.TIME_MS, event.mTimeStamp - stats.beginTime); proto.write(IntervalStatsProto.Event.FLAGS, event.mFlags); proto.write(IntervalStatsProto.Event.TYPE, event.mEventType); + proto.write(IntervalStatsProto.Event.INSTANCE_ID, event.mInstanceId); switch (event.mEventType) { case UsageEvents.Event.CONFIGURATION_CHANGE: if (event.mConfiguration != null) { diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 262125212c14..57dc08fcd253 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -16,6 +16,12 @@ package com.android.server.usage; +import static android.app.usage.UsageEvents.Event.CHOOSER_ACTION; +import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE; +import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK; +import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION; +import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; + import android.Manifest; import android.app.ActivityManager; import android.app.AppOpsManager; @@ -28,10 +34,10 @@ import android.app.usage.ConfigurationStats; import android.app.usage.EventStats; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; -import android.app.usage.UsageStatsManager; -import android.app.usage.UsageStatsManager.StandbyBuckets; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; +import android.app.usage.UsageStatsManager; +import android.app.usage.UsageStatsManager.StandbyBuckets; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -75,9 +81,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.concurrent.TimeUnit; /** * A service that collects, aggregates, and persists application usage data. @@ -106,6 +110,7 @@ public class UsageStatsService extends SystemService implements static final int MSG_FLUSH_TO_DISK = 1; static final int MSG_REMOVE_USER = 2; static final int MSG_UID_STATE_CHANGED = 3; + static final int MSG_REPORT_EVENT_TO_ALL_USERID = 4; private final Object mLock = new Object(); Handler mHandler; @@ -135,12 +140,10 @@ public class UsageStatsService extends SystemService implements @Override public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket, int reason) { - Event event = new Event(); - event.mEventType = Event.STANDBY_BUCKET_CHANGED; + Event event = new Event(Event.STANDBY_BUCKET_CHANGED, + SystemClock.elapsedRealtime()); event.mBucketAndReason = (bucket << 16) | (reason & 0xFFFF); event.mPackage = packageName; - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -397,7 +400,7 @@ public class UsageStatsService extends SystemService implements * Assuming the event's timestamp is measured in milliseconds since boot, * convert it to a system wall time. */ - private void convertToSystemTimeLocked(UsageEvents.Event event) { + private void convertToSystemTimeLocked(Event event) { event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot; } @@ -406,7 +409,6 @@ public class UsageStatsService extends SystemService implements */ void shutdown() { synchronized (mLock) { - mHandler.removeMessages(MSG_REPORT_EVENT); flushToDiskLocked(); } } @@ -414,7 +416,7 @@ public class UsageStatsService extends SystemService implements /** * Called by the Binder stub. */ - void reportEvent(UsageEvents.Event event, int userId) { + void reportEvent(Event event, int userId) { synchronized (mLock) { final long timeNow = checkAndGetTimeLocked(); final long elapsedRealtime = SystemClock.elapsedRealtime(); @@ -431,14 +433,14 @@ public class UsageStatsService extends SystemService implements mAppStandby.reportEvent(event, elapsedRealtime, userId); switch (event.mEventType) { - case Event.MOVE_TO_FOREGROUND: + case Event.ACTIVITY_RESUMED: try { mAppTimeLimit.noteUsageStart(event.getPackageName(), userId); } catch (IllegalArgumentException iae) { Slog.e(TAG, "Failed to note usage start", iae); } break; - case Event.MOVE_TO_BACKGROUND: + case Event.ACTIVITY_PAUSED: try { mAppTimeLimit.noteUsageStop(event.getPackageName(), userId); } catch (IllegalArgumentException iae) { @@ -450,10 +452,31 @@ public class UsageStatsService extends SystemService implements } /** - * Called by the Binder stub. + * Some events like FLUSH_TO_DISK need to be sent to all userId. + * @param event + */ + void reportEventToAllUserId(Event event) { + synchronized (mLock) { + final int userCount = mUserState.size(); + for (int i = 0; i < userCount; i++) { + Event copy = new Event(event); + reportEvent(copy, mUserState.keyAt(i)); + } + } + } + + /** + * Called by the Handler for message MSG_FLUSH_TO_DISK. */ void flushToDisk() { synchronized (mLock) { + // Before flush to disk, report FLUSH_TO_DISK event to signal UsageStats to update app + // usage. In case of abrupt power shutdown like battery drain or cold temperature, + // all UsageStats has correct data up to last flush to disk. + // The FLUSH_TO_DISK event is an internal event, it will not show up in IntervalStats' + // EventList. + Event event = new Event(FLUSH_TO_DISK, SystemClock.elapsedRealtime()); + reportEventToAllUserId(event); flushToDiskLocked(); } } @@ -656,9 +679,11 @@ public class UsageStatsService extends SystemService implements public void handleMessage(Message msg) { switch (msg.what) { case MSG_REPORT_EVENT: - reportEvent((UsageEvents.Event) msg.obj, msg.arg1); + reportEvent((Event) msg.obj, msg.arg1); + break; + case MSG_REPORT_EVENT_TO_ALL_USERID: + reportEventToAllUserId((Event) msg.obj); break; - case MSG_FLUSH_TO_DISK: flushToDisk(); break; @@ -1120,20 +1145,11 @@ public class UsageStatsService extends SystemService implements return; } - UsageEvents.Event event = new UsageEvents.Event(); + Event event = new Event(CHOOSER_ACTION, SystemClock.elapsedRealtime()); event.mPackage = packageName; - - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); - - event.mEventType = Event.CHOOSER_ACTION; - event.mAction = action; - event.mContentType = contentType; - event.mContentAnnotations = annotations; - mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -1251,37 +1267,29 @@ public class UsageStatsService extends SystemService implements private final class LocalService extends UsageStatsManagerInternal { @Override - public void reportEvent(ComponentName component, int userId, int eventType) { + public void reportEvent(ComponentName component, int userId, int eventType, + int instanceId) { if (component == null) { Slog.w(TAG, "Event reported without a component name"); return; } - UsageEvents.Event event = new UsageEvents.Event(); + Event event = new Event(eventType, SystemClock.elapsedRealtime()); event.mPackage = component.getPackageName(); event.mClass = component.getClassName(); - - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); - - event.mEventType = eventType; + event.mInstanceId = instanceId; mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @Override public void reportEvent(String packageName, int userId, int eventType) { if (packageName == null) { - Slog.w(TAG, "Event reported without a package name"); + Slog.w(TAG, "Event reported without a package name, eventType:" + eventType); return; } - UsageEvents.Event event = new UsageEvents.Event(); + Event event = new Event(eventType, SystemClock.elapsedRealtime()); event.mPackage = packageName; - - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); - - event.mEventType = eventType; mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -1292,13 +1300,8 @@ public class UsageStatsService extends SystemService implements return; } - UsageEvents.Event event = new UsageEvents.Event(); + Event event = new Event(CONFIGURATION_CHANGE, SystemClock.elapsedRealtime()); event.mPackage = "android"; - - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); - - event.mEventType = UsageEvents.Event.CONFIGURATION_CHANGE; event.mConfiguration = new Configuration(config); mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -1311,14 +1314,9 @@ public class UsageStatsService extends SystemService implements return; } - UsageEvents.Event event = new UsageEvents.Event(); + Event event = new Event(NOTIFICATION_INTERRUPTION, SystemClock.elapsedRealtime()); event.mPackage = packageName.intern(); event.mNotificationChannelId = channelId.intern(); - - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); - - event.mEventType = Event.NOTIFICATION_INTERRUPTION; mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -1329,14 +1327,9 @@ public class UsageStatsService extends SystemService implements return; } - UsageEvents.Event event = new UsageEvents.Event(); + Event event = new Event(SHORTCUT_INVOCATION, SystemClock.elapsedRealtime()); event.mPackage = packageName.intern(); event.mShortcutId = shortcutId.intern(); - - // This will later be converted to system time. - event.mTimeStamp = SystemClock.elapsedRealtime(); - - event.mEventType = Event.SHORTCUT_INVOCATION; mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @@ -1372,7 +1365,7 @@ public class UsageStatsService extends SystemService implements // This method *WILL* do IO work, but we must block until it is finished or else // we might not shutdown cleanly. This is ok to do with the 'am' lock held, because // we are shutting down. - shutdown(); + UsageStatsService.this.shutdown(); } @Override diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index 01e566cdd85f..ec475bf26bb3 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -61,11 +61,13 @@ final class UsageStatsXmlV1 { private static final String FLAGS_ATTR = "flags"; private static final String CLASS_ATTR = "class"; private static final String TOTAL_TIME_ACTIVE_ATTR = "timeActive"; + private static final String TOTAL_TIME_VISIBLE_ATTR = "timeVisible"; private static final String TOTAL_TIME_SERVICE_USED_ATTR = "timeServiceUsed"; private static final String COUNT_ATTR = "count"; private static final String ACTIVE_ATTR = "active"; private static final String LAST_EVENT_ATTR = "lastEvent"; private static final String TYPE_ATTR = "type"; + private static final String INSTANCE_ID_ATTR = "instanceId"; private static final String SHORTCUT_ID_ATTR = "shortcutId"; private static final String STANDBY_BUCKET_ATTR = "standbyBucket"; private static final String APP_LAUNCH_COUNT_ATTR = "appLaunchCount"; @@ -75,6 +77,7 @@ final class UsageStatsXmlV1 { // Time attributes stored as an offset of the beginTime. private static final String LAST_TIME_ACTIVE_ATTR = "lastTimeActive"; + private static final String LAST_TIME_VISIBLE_ATTR = "lastTimeVisible"; private static final String LAST_TIME_SERVICE_USED_ATTR = "lastTimeServiceUsed"; private static final String END_TIME_ATTR = "endTime"; private static final String TIME_ATTR = "time"; @@ -92,6 +95,13 @@ final class UsageStatsXmlV1 { parser, LAST_TIME_ACTIVE_ATTR); try { + stats.mLastTimeVisible = statsOut.beginTime + XmlUtils.readLongAttribute( + parser, LAST_TIME_VISIBLE_ATTR); + } catch (IOException e) { + Log.e(TAG, "Failed to parse mLastTimeVisible", e); + } + + try { stats.mLastTimeForegroundServiceUsed = statsOut.beginTime + XmlUtils.readLongAttribute( parser, LAST_TIME_SERVICE_USED_ATTR); } catch (IOException e) { @@ -101,8 +111,14 @@ final class UsageStatsXmlV1 { stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR); try { + stats.mTotalTimeVisible = XmlUtils.readLongAttribute(parser, TOTAL_TIME_VISIBLE_ATTR); + } catch (IOException e) { + Log.e(TAG, "Failed to parse mTotalTimeVisible", e); + } + + try { stats.mTotalTimeForegroundServiceUsed = XmlUtils.readLongAttribute(parser, - TOTAL_TIME_SERVICE_USED_ATTR); + TOTAL_TIME_SERVICE_USED_ATTR); } catch (IOException e) { Log.e(TAG, "Failed to parse mTotalTimeForegroundServiceUsed", e); } @@ -199,6 +215,13 @@ final class UsageStatsXmlV1 { event.mTimeStamp = statsOut.beginTime + XmlUtils.readLongAttribute(parser, TIME_ATTR); event.mEventType = XmlUtils.readIntAttribute(parser, TYPE_ATTR); + + try { + event.mInstanceId = XmlUtils.readIntAttribute(parser, INSTANCE_ID_ATTR); + } catch (IOException e) { + Log.e(TAG, "Failed to parse mInstanceId", e); + } + switch (event.mEventType) { case UsageEvents.Event.CONFIGURATION_CHANGE: event.mConfiguration = new Configuration(); @@ -227,10 +250,13 @@ final class UsageStatsXmlV1 { // Write the time offset. XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR, usageStats.mLastTimeUsed - stats.beginTime); + XmlUtils.writeLongAttribute(xml, LAST_TIME_VISIBLE_ATTR, + usageStats.mLastTimeVisible - stats.beginTime); XmlUtils.writeLongAttribute(xml, LAST_TIME_SERVICE_USED_ATTR, usageStats.mLastTimeForegroundServiceUsed - stats.beginTime); XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName); XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground); + XmlUtils.writeLongAttribute(xml, TOTAL_TIME_VISIBLE_ATTR, usageStats.mTotalTimeVisible); XmlUtils.writeLongAttribute(xml, TOTAL_TIME_SERVICE_USED_ATTR, usageStats.mTotalTimeForegroundServiceUsed); XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent); @@ -317,6 +343,7 @@ final class UsageStatsXmlV1 { } XmlUtils.writeIntAttribute(xml, FLAGS_ATTR, event.mFlags); XmlUtils.writeIntAttribute(xml, TYPE_ATTR, event.mEventType); + XmlUtils.writeIntAttribute(xml, INSTANCE_ID_ATTR, event.mInstanceId); switch (event.mEventType) { case UsageEvents.Event.CONFIGURATION_CHANGE: diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 94d7dbb49b74..5128ae1f9130 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -16,10 +16,18 @@ package com.android.server.usage; +import static android.app.usage.UsageStatsManager.INTERVAL_BEST; +import static android.app.usage.UsageStatsManager.INTERVAL_COUNT; +import static android.app.usage.UsageStatsManager.INTERVAL_DAILY; +import static android.app.usage.UsageStatsManager.INTERVAL_MONTHLY; +import static android.app.usage.UsageStatsManager.INTERVAL_WEEKLY; +import static android.app.usage.UsageStatsManager.INTERVAL_YEARLY; + import android.app.usage.ConfigurationStats; import android.app.usage.EventList; import android.app.usage.EventStats; import android.app.usage.UsageEvents; +import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.content.Context; @@ -29,6 +37,7 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import android.util.SparseIntArray; import com.android.internal.util.IndentingPrintWriter; import com.android.server.usage.UsageStatsDatabase.StatCombiner; @@ -65,7 +74,7 @@ class UserUsageStatsService { private final int mUserId; // STOPSHIP: Temporary member variable for debugging b/110930764. - private UsageEvents.Event mLastEvent; + private Event mLastEvent; private static final long[] INTERVAL_LENGTH = new long[] { UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS, @@ -87,7 +96,7 @@ class UserUsageStatsService { mContext = context; mDailyExpiryDate = new UnixCalendar(0); mDatabase = new UsageStatsDatabase(usageStatsDir); - mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT]; + mCurrentStats = new IntervalStats[INTERVAL_COUNT]; mListener = listener; mLogPrefix = "User[" + Integer.toString(userId) + "] "; mUserId = userId; @@ -125,28 +134,6 @@ class UserUsageStatsService { updateRolloverDeadline(); } - // Now close off any events that were open at the time this was saved. - for (IntervalStats stat : mCurrentStats) { - final int pkgCount = stat.packageStats.size(); - for (int i = 0; i < pkgCount; i++) { - final UsageStats pkgStats = stat.packageStats.valueAt(i); - if (!pkgStats.mLastForegroundActivityEventMap.isEmpty() - || !pkgStats.mLastForegroundServiceEventMap.isEmpty()) { - if (!pkgStats.mLastForegroundActivityEventMap.isEmpty()) { - stat.update(pkgStats.mPackageName, null, stat.lastTimeSaved, - UsageEvents.Event.END_OF_DAY); - } - if (!pkgStats.mLastForegroundServiceEventMap.isEmpty()) { - stat.update(pkgStats.mPackageName, null , stat.lastTimeSaved, - UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE); - } - notifyStatsChanged(); - } - } - - stat.updateConfigurationStats(null, stat.lastTimeSaved); - } - if (mDatabase.isNewUpdate()) { notifyNewUpdate(); } @@ -158,40 +145,46 @@ class UserUsageStatsService { loadActiveStats(newTime); } - void reportEvent(UsageEvents.Event event) { + void reportEvent(Event event) { if (DEBUG) { Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage + "[" + event.mTimeStamp + "]: " + eventToString(event.mEventType)); } - mLastEvent = new UsageEvents.Event(event); + mLastEvent = new Event(event); if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) { // Need to rollover rolloverStats(event.mTimeStamp); } - final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY]; + final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY]; final Configuration newFullConfig = event.mConfiguration; - if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE && - currentDailyStats.activeConfiguration != null) { + if (event.mEventType == Event.CONFIGURATION_CHANGE + && currentDailyStats.activeConfiguration != null) { // Make the event configuration a delta. event.mConfiguration = Configuration.generateDelta( currentDailyStats.activeConfiguration, newFullConfig); } - if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) { + if (event.mEventType != Event.SYSTEM_INTERACTION + // ACTIVITY_DESTROYED is a private event. If there is preceding ACTIVITY_STOPPED + // ACTIVITY_DESTROYED will be dropped. Otherwise it will be converted to + // ACTIVITY_STOPPED. + && event.mEventType != Event.ACTIVITY_DESTROYED + // FLUSH_TO_DISK is a private event. + && event.mEventType != Event.FLUSH_TO_DISK) { currentDailyStats.addEvent(event); } boolean incrementAppLaunch = false; - if (event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND) { + if (event.mEventType == Event.ACTIVITY_RESUMED) { if (event.mPackage != null && !event.mPackage.equals(mLastBackgroundedPackage)) { incrementAppLaunch = true; } - } else if (event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND) { + } else if (event.mEventType == Event.ACTIVITY_PAUSED) { if (event.mPackage != null) { mLastBackgroundedPackage = event.mPackage; } @@ -199,10 +192,10 @@ class UserUsageStatsService { for (IntervalStats stats : mCurrentStats) { switch (event.mEventType) { - case UsageEvents.Event.CONFIGURATION_CHANGE: { + case Event.CONFIGURATION_CHANGE: { stats.updateConfigurationStats(newFullConfig, event.mTimeStamp); } break; - case UsageEvents.Event.CHOOSER_ACTION: { + case Event.CHOOSER_ACTION: { stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction); String[] annotations = event.mContentAnnotations; if (annotations != null) { @@ -211,21 +204,21 @@ class UserUsageStatsService { } } } break; - case UsageEvents.Event.SCREEN_INTERACTIVE: { + case Event.SCREEN_INTERACTIVE: { stats.updateScreenInteractive(event.mTimeStamp); } break; - case UsageEvents.Event.SCREEN_NON_INTERACTIVE: { + case Event.SCREEN_NON_INTERACTIVE: { stats.updateScreenNonInteractive(event.mTimeStamp); } break; - case UsageEvents.Event.KEYGUARD_SHOWN: { + case Event.KEYGUARD_SHOWN: { stats.updateKeyguardShown(event.mTimeStamp); } break; - case UsageEvents.Event.KEYGUARD_HIDDEN: { + case Event.KEYGUARD_HIDDEN: { stats.updateKeyguardHidden(event.mTimeStamp); } break; default: { stats.update(event.mPackage, event.getClassName(), - event.mTimeStamp, event.mEventType); + event.mTimeStamp, event.mEventType, event.mInstanceId); if (incrementAppLaunch) { stats.incrementAppLaunchCount(event.mPackage); } @@ -286,12 +279,12 @@ class UserUsageStatsService { */ private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime, StatCombiner<T> combiner) { - if (intervalType == UsageStatsManager.INTERVAL_BEST) { + if (intervalType == INTERVAL_BEST) { intervalType = mDatabase.findBestFitBucket(beginTime, endTime); if (intervalType < 0) { // Nothing saved to disk yet, so every stat is just as equal (no rollover has // occurred. - intervalType = UsageStatsManager.INTERVAL_DAILY; + intervalType = INTERVAL_DAILY; } } @@ -316,10 +309,10 @@ class UserUsageStatsService { } // STOPSHIP: Temporary logging for b/110930764. - if (intervalType == UsageStatsManager.INTERVAL_DAILY + if (intervalType == INTERVAL_DAILY && mLastEvent != null && mLastEvent.mTimeStamp >= beginTime) { final IntervalStats diskStats = mDatabase.getLatestUsageStats( - UsageStatsManager.INTERVAL_DAILY); + INTERVAL_DAILY); StringBuilder sb = new StringBuilder(256); sb.append("Last 24 hours of UsageStats missing! timeRange : "); sb.append(beginTime); @@ -395,11 +388,11 @@ class UserUsageStatsService { UsageEvents queryEvents(final long beginTime, final long endTime, boolean obfuscateInstantApps) { final ArraySet<String> names = new ArraySet<>(); - List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY, - beginTime, endTime, new StatCombiner<UsageEvents.Event>() { + List<Event> results = queryStats(INTERVAL_DAILY, + beginTime, endTime, new StatCombiner<Event>() { @Override public void combine(IntervalStats stats, boolean mutable, - List<UsageEvents.Event> accumulatedResult) { + List<Event> accumulatedResult) { if (stats.events == null) { return; } @@ -411,11 +404,13 @@ class UserUsageStatsService { return; } - UsageEvents.Event event = stats.events.get(i); + Event event = stats.events.get(i); if (obfuscateInstantApps) { event = event.getObfuscatedIfInstantApp(); } - names.add(event.mPackage); + if (event.mPackage != null) { + names.add(event.mPackage); + } if (event.mClass != null) { names.add(event.mClass); } @@ -437,7 +432,7 @@ class UserUsageStatsService { final String packageName) { final ArraySet<String> names = new ArraySet<>(); names.add(packageName); - final List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY, + final List<Event> results = queryStats(INTERVAL_DAILY, beginTime, endTime, (stats, mutable, accumulatedResult) -> { if (stats.events == null) { return; @@ -450,7 +445,7 @@ class UserUsageStatsService { return; } - final UsageEvents.Event event = stats.events.get(i); + final Event event = stats.events.get(i); if (!packageName.equals(event.mPackage)) { continue; } @@ -492,33 +487,33 @@ class UserUsageStatsService { // Make a note of which components need a new CONTINUE_PREVIOUS_DAY or // CONTINUING_FOREGROUND_SERVICE entry. final Configuration previousConfig = - mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration; - ArraySet<String> continuePreviousDay = new ArraySet<>(); - ArrayMap<String, ArrayMap<String, Integer>> continuePreviousDayForegroundActivity = + mCurrentStats[INTERVAL_DAILY].activeConfiguration; + ArraySet<String> continuePkgs = new ArraySet<>(); + ArrayMap<String, SparseIntArray> continueActivity = new ArrayMap<>(); - ArrayMap<String, ArrayMap<String, Integer>> continuePreviousDayForegroundService = + ArrayMap<String, ArrayMap<String, Integer>> continueForegroundService = new ArrayMap<>(); for (IntervalStats stat : mCurrentStats) { final int pkgCount = stat.packageStats.size(); for (int i = 0; i < pkgCount; i++) { final UsageStats pkgStats = stat.packageStats.valueAt(i); - if (!pkgStats.mLastForegroundActivityEventMap.isEmpty() - || !pkgStats.mLastForegroundServiceEventMap.isEmpty()) { - if (!pkgStats.mLastForegroundActivityEventMap.isEmpty()) { - continuePreviousDayForegroundActivity.put(pkgStats.mPackageName, - pkgStats.mLastForegroundActivityEventMap); + if (pkgStats.mActivities.size() > 0 + || !pkgStats.mForegroundServices.isEmpty()) { + if (pkgStats.mActivities.size() > 0) { + continueActivity.put(pkgStats.mPackageName, + pkgStats.mActivities); stat.update(pkgStats.mPackageName, null, mDailyExpiryDate.getTimeInMillis() - 1, - UsageEvents.Event.END_OF_DAY); + Event.END_OF_DAY, 0); } - if (!pkgStats.mLastForegroundServiceEventMap.isEmpty()) { - continuePreviousDayForegroundService.put(pkgStats.mPackageName, - pkgStats.mLastForegroundServiceEventMap); + if (!pkgStats.mForegroundServices.isEmpty()) { + continueForegroundService.put(pkgStats.mPackageName, + pkgStats.mForegroundServices); stat.update(pkgStats.mPackageName, null, mDailyExpiryDate.getTimeInMillis() - 1, - UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE); + Event.ROLLOVER_FOREGROUND_SERVICE, 0); } - continuePreviousDay.add(pkgStats.mPackageName); + continuePkgs.add(pkgStats.mPackageName); notifyStatsChanged(); } } @@ -532,27 +527,27 @@ class UserUsageStatsService { mDatabase.prune(currentTimeMillis); loadActiveStats(currentTimeMillis); - final int continueCount = continuePreviousDay.size(); + final int continueCount = continuePkgs.size(); for (int i = 0; i < continueCount; i++) { - String pkgName = continuePreviousDay.valueAt(i); - final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime; + String pkgName = continuePkgs.valueAt(i); + final long beginTime = mCurrentStats[INTERVAL_DAILY].beginTime; for (IntervalStats stat : mCurrentStats) { - if (continuePreviousDayForegroundActivity.containsKey(pkgName)) { - final ArrayMap<String, Integer> foregroundActivityEventMap = - continuePreviousDayForegroundActivity.get(pkgName); - final int size = foregroundActivityEventMap.size(); + if (continueActivity.containsKey(pkgName)) { + final SparseIntArray eventMap = + continueActivity.get(pkgName); + final int size = eventMap.size(); for (int j = 0; j < size; j++) { - stat.update(pkgName, foregroundActivityEventMap.keyAt(j), beginTime, - UsageEvents.Event.CONTINUE_PREVIOUS_DAY); + stat.update(pkgName, null, beginTime, + eventMap.valueAt(j), eventMap.keyAt(j)); } } - if (continuePreviousDayForegroundService.containsKey(pkgName)) { - final ArrayMap<String, Integer> foregroundServiceEventMap = - continuePreviousDayForegroundService.get(pkgName); - final int size = foregroundServiceEventMap.size(); + if (continueForegroundService.containsKey(pkgName)) { + final ArrayMap<String, Integer> eventMap = + continueForegroundService.get(pkgName); + final int size = eventMap.size(); for (int j = 0; j < size; j++) { - stat.update(pkgName, foregroundServiceEventMap.keyAt(j), beginTime, - UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE); + stat.update(pkgName, eventMap.keyAt(j), beginTime, + eventMap.valueAt(j), 0); } } stat.updateConfigurationStats(previousConfig, beginTime); @@ -611,7 +606,7 @@ class UserUsageStatsService { private void updateRolloverDeadline() { mDailyExpiryDate.setTimeInMillis( - mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime); + mCurrentStats[INTERVAL_DAILY].beginTime); mDailyExpiryDate.addDays(1); Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " + sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" + @@ -660,7 +655,7 @@ class UserUsageStatsService { } - void printEvent(IndentingPrintWriter pw, UsageEvents.Event event, boolean prettyDates) { + void printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates) { pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates)); pw.printPair("type", eventToString(event.mEventType)); pw.printPair("package", event.mPackage); @@ -673,10 +668,15 @@ class UserUsageStatsService { if (event.mShortcutId != null) { pw.printPair("shortcutId", event.mShortcutId); } - if (event.mEventType == UsageEvents.Event.STANDBY_BUCKET_CHANGED) { + if (event.mEventType == Event.STANDBY_BUCKET_CHANGED) { pw.printPair("standbyBucket", event.getStandbyBucket()); pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason())); + } else if (event.mEventType == Event.ACTIVITY_RESUMED + || event.mEventType == Event.ACTIVITY_PAUSED + || event.mEventType == Event.ACTIVITY_STOPPED) { + pw.printPair("instanceId", event.getInstanceId()); } + if (event.mNotificationChannelId != null) { pw.printPair("channelId", event.mNotificationChannelId); } @@ -691,11 +691,11 @@ class UserUsageStatsService { final long beginTime = yesterday.getTimeInMillis(); - List<UsageEvents.Event> events = queryStats(UsageStatsManager.INTERVAL_DAILY, - beginTime, endTime, new StatCombiner<UsageEvents.Event>() { + List<Event> events = queryStats(INTERVAL_DAILY, + beginTime, endTime, new StatCombiner<Event>() { @Override public void combine(IntervalStats stats, boolean mutable, - List<UsageEvents.Event> accumulatedResult) { + List<Event> accumulatedResult) { if (stats.events == null) { return; } @@ -707,7 +707,7 @@ class UserUsageStatsService { return; } - UsageEvents.Event event = stats.events.get(i); + Event event = stats.events.get(i); if (pkg != null && !pkg.equals(event.mPackage)) { continue; } @@ -727,7 +727,7 @@ class UserUsageStatsService { pw.println(")"); if (events != null) { pw.increaseIndent(); - for (UsageEvents.Event event : events) { + for (Event event : events) { printEvent(pw, event, prettyDates); } pw.decreaseIndent(); @@ -772,9 +772,17 @@ class UserUsageStatsService { continue; } pw.printPair("package", usageStats.mPackageName); - pw.printPair("totalTime", + pw.printPair("totalTimeUsed", formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates)); - pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates)); + pw.printPair("lastTimeUsed", formatDateTime(usageStats.mLastTimeUsed, prettyDates)); + pw.printPair("totalTimeVisible", + formatElapsedTime(usageStats.mTotalTimeVisible, prettyDates)); + pw.printPair("lastTimeVisible", + formatDateTime(usageStats.mLastTimeVisible, prettyDates)); + pw.printPair("totalTimeFS", + formatElapsedTime(usageStats.mTotalTimeForegroundServiceUsed, prettyDates)); + pw.printPair("lastTimeFS", + formatDateTime(usageStats.mLastTimeForegroundServiceUsed, prettyDates)); pw.printPair("appLaunchCount", usageStats.mAppLaunchCount); pw.println(); } @@ -845,7 +853,7 @@ class UserUsageStatsService { final EventList events = stats.events; final int eventCount = events != null ? events.size() : 0; for (int i = 0; i < eventCount; i++) { - final UsageEvents.Event event = events.get(i); + final Event event = events.get(i); if (pkg != null && !pkg.equals(event.mPackage)) { continue; } @@ -858,13 +866,13 @@ class UserUsageStatsService { private static String intervalToString(int interval) { switch (interval) { - case UsageStatsManager.INTERVAL_DAILY: + case INTERVAL_DAILY: return "daily"; - case UsageStatsManager.INTERVAL_WEEKLY: + case INTERVAL_WEEKLY: return "weekly"; - case UsageStatsManager.INTERVAL_MONTHLY: + case INTERVAL_MONTHLY: return "monthly"; - case UsageStatsManager.INTERVAL_YEARLY: + case INTERVAL_YEARLY: return "yearly"; default: return "?"; @@ -873,47 +881,49 @@ class UserUsageStatsService { private static String eventToString(int eventType) { switch (eventType) { - case UsageEvents.Event.NONE: + case Event.NONE: return "NONE"; - case UsageEvents.Event.MOVE_TO_BACKGROUND: - return "MOVE_TO_BACKGROUND"; - case UsageEvents.Event.MOVE_TO_FOREGROUND: - return "MOVE_TO_FOREGROUND"; - case UsageEvents.Event.FOREGROUND_SERVICE_START: + case Event.ACTIVITY_PAUSED: + return "ACTIVITY_PAUSED"; + case Event.ACTIVITY_RESUMED: + return "ACTIVITY_RESUMED"; + case Event.FOREGROUND_SERVICE_START: return "FOREGROUND_SERVICE_START"; - case UsageEvents.Event.FOREGROUND_SERVICE_STOP: + case Event.FOREGROUND_SERVICE_STOP: return "FOREGROUND_SERVICE_STOP"; - case UsageEvents.Event.END_OF_DAY: + case Event.ACTIVITY_STOPPED: + return "ACTIVITY_STOPPED"; + case Event.END_OF_DAY: return "END_OF_DAY"; - case UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE: + case Event.ROLLOVER_FOREGROUND_SERVICE: return "ROLLOVER_FOREGROUND_SERVICE"; - case UsageEvents.Event.CONTINUE_PREVIOUS_DAY: + case Event.CONTINUE_PREVIOUS_DAY: return "CONTINUE_PREVIOUS_DAY"; - case UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE: + case Event.CONTINUING_FOREGROUND_SERVICE: return "CONTINUING_FOREGROUND_SERVICE"; - case UsageEvents.Event.CONFIGURATION_CHANGE: + case Event.CONFIGURATION_CHANGE: return "CONFIGURATION_CHANGE"; - case UsageEvents.Event.SYSTEM_INTERACTION: + case Event.SYSTEM_INTERACTION: return "SYSTEM_INTERACTION"; - case UsageEvents.Event.USER_INTERACTION: + case Event.USER_INTERACTION: return "USER_INTERACTION"; - case UsageEvents.Event.SHORTCUT_INVOCATION: + case Event.SHORTCUT_INVOCATION: return "SHORTCUT_INVOCATION"; - case UsageEvents.Event.CHOOSER_ACTION: + case Event.CHOOSER_ACTION: return "CHOOSER_ACTION"; - case UsageEvents.Event.NOTIFICATION_SEEN: + case Event.NOTIFICATION_SEEN: return "NOTIFICATION_SEEN"; - case UsageEvents.Event.STANDBY_BUCKET_CHANGED: + case Event.STANDBY_BUCKET_CHANGED: return "STANDBY_BUCKET_CHANGED"; - case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + case Event.NOTIFICATION_INTERRUPTION: return "NOTIFICATION_INTERRUPTION"; - case UsageEvents.Event.SLICE_PINNED: + case Event.SLICE_PINNED: return "SLICE_PINNED"; - case UsageEvents.Event.SLICE_PINNED_PRIV: + case Event.SLICE_PINNED_PRIV: return "SLICE_PINNED_PRIV"; - case UsageEvents.Event.SCREEN_INTERACTIVE: + case Event.SCREEN_INTERACTIVE: return "SCREEN_INTERACTIVE"; - case UsageEvents.Event.SCREEN_NON_INTERACTIVE: + case Event.SCREEN_NON_INTERACTIVE: return "SCREEN_NON_INTERACTIVE"; case UsageEvents.Event.KEYGUARD_SHOWN: return "KEYGUARD_SHOWN"; diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index 613ba0044f6a..904d55e6d0f3 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -32,7 +32,9 @@ import android.service.usb.UsbConnectionRecordProto; import android.service.usb.UsbHostManagerProto; import android.service.usb.UsbIsHeadsetProto; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Slog; +import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; @@ -86,6 +88,7 @@ public class UsbHostManager { private int mNumConnects; // TOTAL # of connect/disconnect private final LinkedList<ConnectionRecord> mConnections = new LinkedList<ConnectionRecord>(); private ConnectionRecord mLastConnect; + private final ArrayMap<String, ConnectionRecord> mConnected = new ArrayMap<>(); /* * ConnectionRecord @@ -300,6 +303,11 @@ public class UsbHostManager { if (mode != ConnectionRecord.DISCONNECT) { mLastConnect = rec; } + if (mode == ConnectionRecord.CONNECT) { + mConnected.put(deviceAddress, rec); + } else if (mode == ConnectionRecord.DISCONNECT) { + mConnected.remove(deviceAddress); + } } private void logUsbDevice(UsbDescriptorParser descriptorParser) { @@ -408,6 +416,14 @@ public class UsbHostManager { // Tracking addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT, parser.getRawDescriptors()); + + // Stats collection + if (parser.hasAudioInterface()) { + StatsLog.write(StatsLog.USB_DEVICE_ATTACHED, newDevice.getVendorId(), + newDevice.getProductId(), parser.hasAudioInterface(), + parser.hasHIDInterface(), parser.hasStorageInterface(), + StatsLog.USB_DEVICE_ATTACHED__STATE__STATE_CONNECTED, 0); + } } } @@ -432,9 +448,22 @@ public class UsbHostManager { mUsbAlsaManager.usbDeviceRemoved(deviceAddress); mSettingsManager.usbDeviceRemoved(device); getCurrentUserSettings().usbDeviceRemoved(device); - + ConnectionRecord current = mConnected.get(deviceAddress); // Tracking addConnectionRecord(deviceAddress, ConnectionRecord.DISCONNECT, null); + + if (current != null) { + UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, + current.mDescriptors); + if (parser.hasAudioInterface()) { + // Stats collection + StatsLog.write(StatsLog.USB_DEVICE_ATTACHED, device.getVendorId(), + device.getProductId(), parser.hasAudioInterface(), + parser.hasHIDInterface(), parser.hasStorageInterface(), + StatsLog.USB_DEVICE_ATTACHED__STATE__STATE_DISCONNECTED, + System.currentTimeMillis() - current.mTimestamp); + } + } } else { Slog.d(TAG, "Removed device at " + deviceAddress + " was already gone"); } diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index 33da4033174a..96618f569928 100644 --- a/services/usb/java/com/android/server/usb/UsbPortManager.java +++ b/services/usb/java/com/android/server/usb/UsbPortManager.java @@ -41,12 +41,14 @@ import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.service.usb.UsbPortInfoProto; import android.service.usb.UsbPortManagerProto; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; +import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; @@ -54,6 +56,7 @@ import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.FgThread; import java.util.ArrayList; +import java.util.HashMap; import java.util.NoSuchElementException; /** @@ -117,6 +120,10 @@ public class UsbPortManager { private final ArrayMap<String, RawPortInfo> mSimulatedPorts = new ArrayMap<>(); + // Maintains the current connected status of the port. + // Uploads logs only when the connection status is changes. + private final HashMap<String, Boolean> mConnected = new HashMap<>(); + public UsbPortManager(Context context) { mContext = context; try { @@ -700,6 +707,18 @@ public class UsbPortManager { // Guard against possible reentrance by posting the broadcast from the handler // instead of from within the critical section. mHandler.post(() -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL)); + + // Log to statsd + if (!mConnected.containsKey(portInfo.mUsbPort.getId()) + || (mConnected.get(portInfo.mUsbPort.getId()) + != portInfo.mUsbPortStatus.isConnected())) { + mConnected.put(portInfo.mUsbPort.getId(), portInfo.mUsbPortStatus.isConnected()); + StatsLog.write(StatsLog.USB_CONNECTOR_STATE_CHANGED, + portInfo.mUsbPortStatus.isConnected() + ? StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_CONNECTED : + StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED, + portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis); + } } private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) { @@ -746,7 +765,12 @@ public class UsbPortManager { public boolean mCanChangeMode; public boolean mCanChangePowerRole; public boolean mCanChangeDataRole; - public int mDisposition; // default initialized to 0 which means added + // default initialized to 0 which means added + public int mDisposition; + // Tracks elapsedRealtime() of when the port was connected + public long mConnectedAtMillis; + // 0 when port is connected. Else reports the last connected duration + public long mLastConnectDurationMillis; public PortInfo(String portId, int supportedModes) { mUsbPort = new UsbPort(portId, supportedModes); @@ -756,6 +780,8 @@ public class UsbPortManager { int currentPowerRole, boolean canChangePowerRole, int currentDataRole, boolean canChangeDataRole, int supportedRoleCombinations) { + boolean dispositionChanged = false; + mCanChangeMode = canChangeMode; mCanChangePowerRole = canChangePowerRole; mCanChangeDataRole = canChangeDataRole; @@ -767,9 +793,18 @@ public class UsbPortManager { != supportedRoleCombinations) { mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations); - return true; + dispositionChanged = true; } - return false; + + if (mUsbPortStatus.isConnected() && mConnectedAtMillis == 0) { + mConnectedAtMillis = SystemClock.elapsedRealtime(); + mLastConnectDurationMillis = 0; + } else if (!mUsbPortStatus.isConnected() && mConnectedAtMillis != 0) { + mLastConnectDurationMillis = SystemClock.elapsedRealtime() - mConnectedAtMillis; + mConnectedAtMillis = 0; + } + + return dispositionChanged; } void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) { @@ -782,6 +817,10 @@ public class UsbPortManager { mCanChangePowerRole); dump.write("can_change_data_role", UsbPortInfoProto.CAN_CHANGE_DATA_ROLE, mCanChangeDataRole); + dump.write("connected_at_millis", + UsbPortInfoProto.CONNECTED_AT_MILLIS, mConnectedAtMillis); + dump.write("last_connect_duration_millis", + UsbPortInfoProto.LAST_CONNECT_DURATION_MILLIS, mLastConnectDurationMillis); dump.end(token); } @@ -791,7 +830,9 @@ public class UsbPortManager { return "port=" + mUsbPort + ", status=" + mUsbPortStatus + ", canChangeMode=" + mCanChangeMode + ", canChangePowerRole=" + mCanChangePowerRole - + ", canChangeDataRole=" + mCanChangeDataRole; + + ", canChangeDataRole=" + mCanChangeDataRole + + ", connectedAtMillis=" + mConnectedAtMillis + + ", lastConnectDurationMillis=" + mLastConnectDurationMillis; } } diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index d617de0af6a1..36d0188048c3 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -122,10 +122,21 @@ public final class Call { * The key to retrieve the optional {@code PhoneAccount}s Telecom can bundle with its Call * extras. Used to pass the phone accounts to display on the front end to the user in order to * select phone accounts to (for example) place a call. + * @deprecated Use the list from {@link #EXTRA_SUGGESTED_PHONE_ACCOUNTS} instead. */ + @Deprecated public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts"; /** + * Key for extra used to pass along a list of {@link PhoneAccountSuggestion}s to the in-call + * UI when a call enters the {@link #STATE_SELECT_PHONE_ACCOUNT} state. The list included here + * will have the same length and be in the same order as the list passed with + * {@link #AVAILABLE_PHONE_ACCOUNTS}. + */ + public static final String EXTRA_SUGGESTED_PHONE_ACCOUNTS = + "android.telecom.extra.SUGGESTED_PHONE_ACCOUNTS"; + + /** * Extra key used to indicate the time (in milliseconds since midnight, January 1, 1970 UTC) * when the last outgoing emergency call was made. This is used to identify potential emergency * callbacks. diff --git a/telecomm/java/android/telecom/PhoneAccountSuggestion.java b/telecomm/java/android/telecom/PhoneAccountSuggestion.java new file mode 100644 index 000000000000..4e6a178c8170 --- /dev/null +++ b/telecomm/java/android/telecom/PhoneAccountSuggestion.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2018 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.telecom; + +import android.annotation.IntDef; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public final class PhoneAccountSuggestion implements Parcelable { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {REASON_NONE, REASON_INTRA_CARRIER, REASON_FREQUENT, + REASON_USER_SET, REASON_OTHER}, prefix = { "REASON_" }) + public @interface SuggestionReason {} + + /** + * Indicates that this account is not suggested for use, but is still available. + */ + public static final int REASON_NONE = 0; + + /** + * Indicates that the {@link PhoneAccountHandle} is suggested because the number we're calling + * is on the same carrier, and therefore may have lower rates. + */ + public static final int REASON_INTRA_CARRIER = 1; + + /** + * Indicates that the {@link PhoneAccountHandle} is suggested because the user uses it + * frequently for the number that we are calling. + */ + public static final int REASON_FREQUENT = 2; + + /** + * Indicates that the {@link PhoneAccountHandle} is suggested because the user explicitly + * specified that it be used for the number we are calling. + */ + public static final int REASON_USER_SET = 3; + + /** + * Indicates that the {@link PhoneAccountHandle} is suggested for a reason not otherwise + * enumerated here. + */ + public static final int REASON_OTHER = 4; + + private PhoneAccountHandle mHandle; + private int mReason; + private boolean mShouldAutoSelect; + + /** + * @hide + */ + @SystemApi + @TestApi + public PhoneAccountSuggestion(PhoneAccountHandle handle, @SuggestionReason int reason, + boolean shouldAutoSelect) { + this.mHandle = handle; + this.mReason = reason; + this.mShouldAutoSelect = shouldAutoSelect; + } + + private PhoneAccountSuggestion(Parcel in) { + mHandle = in.readParcelable(PhoneAccountHandle.class.getClassLoader()); + mReason = in.readInt(); + mShouldAutoSelect = in.readByte() != 0; + } + + public static final Creator<PhoneAccountSuggestion> CREATOR = + new Creator<PhoneAccountSuggestion>() { + @Override + public PhoneAccountSuggestion createFromParcel(Parcel in) { + return new PhoneAccountSuggestion(in); + } + + @Override + public PhoneAccountSuggestion[] newArray(int size) { + return new PhoneAccountSuggestion[size]; + } + }; + + /** + * @return The {@link PhoneAccountHandle} for this suggestion. + */ + public PhoneAccountHandle getPhoneAccountHandle() { + return mHandle; + } + + /** + * @return The reason for this suggestion + */ + public @SuggestionReason int getReason() { + return mReason; + } + + /** + * Suggests whether the dialer should automatically place the call using this account without + * user interaction. This may be set on multiple {@link PhoneAccountSuggestion}s, and the dialer + * is free to choose which one to use. + * @return {@code true} if the hint is to auto-select, {@code false} otherwise. + */ + public boolean shouldAutoSelect() { + return mShouldAutoSelect; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mHandle, flags); + dest.writeInt(mReason); + dest.writeByte((byte) (mShouldAutoSelect ? 1 : 0)); + } +} diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 9f0bdd715359..49030f59064a 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -460,6 +460,12 @@ public class TelecomManager { "android.telecom.extra.START_CALL_WITH_RTT"; /** + * A boolean extra set to indicate whether an app is eligible to be bound to when there are + * ongoing calls on the device. + */ + public static final String EXTRA_IS_ENABLED = "android.telecom.extra.IS_ENABLED"; + + /** * A boolean meta-data value indicating whether an {@link InCallService} implements an * in-call user interface. Dialer implementations (see {@link #getDefaultDialerPackage()}) which * would also like to replace the in-call interface should set this meta-data to {@code true} in @@ -471,8 +477,6 @@ public class TelecomManager { * A boolean meta-data value indicating whether an {@link InCallService} implements an * in-call user interface to be used while the device is in car-mode (see * {@link android.content.res.Configuration.UI_MODE_TYPE_CAR}). - * - * @hide */ public static final String METADATA_IN_CALL_SERVICE_CAR_MODE_UI = "android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"; @@ -1462,7 +1466,6 @@ public class TelecomManager { * otherwise. */ @RequiresPermission(Manifest.permission.ANSWER_PHONE_CALLS) - @SystemApi public boolean endCall() { try { if (isServiceConnected()) { @@ -1539,7 +1542,6 @@ public class TelecomManager { /** * Returns whether TTY is supported on this device. */ - @SystemApi @RequiresPermission(anyOf = { android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE @@ -2040,7 +2042,6 @@ public class TelecomManager { } catch (RemoteException e) { Log.e(TAG, "RemoteException handleCallIntent: " + e); } - } private ITelecomService getTelecomService() { diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 143b323892f3..d64efea5fa63 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -305,6 +305,8 @@ interface ITelecomService { */ void handleCallIntent(in Intent intent); + void setTestDefaultCallRedirectionApp(String packageName); + void setTestDefaultCallScreeningApp(String packageName); void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded); diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index 6a396ce33093..1cbe5a26caed 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -3640,8 +3640,9 @@ public final class Telephony { /** * Generates a content {@link Uri} used to receive updates on precise carrier identity - * change on the given subscriptionId - * {@link TelephonyManager#ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED}. + * change on the given subscriptionId returned by + * {@link TelephonyManager#getSimPreciseCarrierId()}. + * @see TelephonyManager#ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED * <p> * Use this {@link Uri} with a {@link ContentObserver} to be notified of changes to the * precise carrier identity {@link TelephonyManager#getSimPreciseCarrierId()} @@ -3652,7 +3653,6 @@ public final class Telephony { * * @param subscriptionId the subscriptionId to receive updates on * @return the Uri used to observe precise carrier identity changes - * @hide */ public static Uri getPreciseCarrierIdUriForSubscriptionId(int subscriptionId) { return Uri.withAppendedPath(Uri.withAppendedPath(CONTENT_URI, "precise"), @@ -3674,22 +3674,20 @@ public final class Telephony { public static final String CARRIER_ID = "carrier_id"; /** - * A user facing carrier name for precise carrier id. - * @see TelephonyManager#getSimPreciseCarrierIdName() + * A fine-grained carrier id. + * @see TelephonyManager#getSimPreciseCarrierId() * This is not a database column, only used to notify content observers for * {@link #getPreciseCarrierIdUriForSubscriptionId(int)} - * @hide */ - public static final String PRECISE_CARRIER_ID_NAME = "precise_carrier_id_name"; + public static final String PRECISE_CARRIER_ID = "precise_carrier_id"; /** - * A fine-grained carrier id. - * @see TelephonyManager#getSimPreciseCarrierId() + * A user facing carrier name for precise carrier id {@link #PRECISE_CARRIER_ID}. + * @see TelephonyManager#getSimPreciseCarrierIdName() * This is not a database column, only used to notify content observers for * {@link #getPreciseCarrierIdUriForSubscriptionId(int)} - * @hide */ - public static final String PRECISE_CARRIER_ID = "precise_carrier_id"; + public static final String PRECISE_CARRIER_ID_NAME = "precise_carrier_id_name"; /** * A unique parent carrier id. The parent-child @@ -3703,18 +3701,6 @@ public final class Telephony { public static final String PARENT_CARRIER_ID = "parent_carrier_id"; /** - * A unique mno carrier id. mno carrier shares the same {@link All#MCCMNC} as carrier id - * and can be solely identified by {@link All#MCCMNC} only. If there is no such mno - * carrier, then mno carrier id equals to {@link #CARRIER_ID carrier id}. - * - * <p>mno carrier id can be used as fallback id. When the exact carrier id configurations - * are not found, usually fall back to its mno carrier id. - * <P>Type: INTEGER </P> - * @hide - */ - public static final String MNO_CARRIER_ID = "mno_carrier_id"; - - /** * Contains mappings between matching rules with carrier id for all carriers. * @hide */ diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 62cead17ac54..cfe134fd0ee2 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -68,7 +68,13 @@ public class CarrierConfigManager { * This intent is broadcast by the system when carrier config changes. An int is specified in * {@link #EXTRA_SLOT_INDEX} to indicate the slot index that this is for. An optional int extra * {@link #EXTRA_SUBSCRIPTION_INDEX} is included to indicate the subscription index if a valid - * one is available for the slot index. + * one is available for the slot index. An optional int extra + * {@link TelephonyManager#EXTRA_CARRIER_ID} is included to indicate the carrier id for the + * changed carrier configuration. An optional int extra + * {@link TelephonyManager#EXTRA_PRECISE_CARRIER_ID} is included to indicate the precise + * carrier id for the changed carrier configuration. + * @see TelephonyManager#getSimCarrierId() + * @see TelephonyManager#getSimPreciseCarrierId() */ public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; @@ -78,6 +84,14 @@ public class CarrierConfigManager { // system image, that can be added in packages/apps/CarrierConfig. /** + * Specifies a value that identifies the version of the carrier configuration that is + * currently in use. This string is displayed on the UI. + * The format of the string is not specified. + */ + public static final String KEY_CARRIER_CONFIG_VERSION_STRING = + "carrier_config_version_string"; + + /** * This flag specifies whether VoLTE availability is based on provisioning. By default this is * false. */ @@ -394,7 +408,6 @@ public class CarrierConfigManager { * @see SubscriptionManager#getSubscriptionPlans(int) * @see SubscriptionManager#setSubscriptionPlans(int, java.util.List) */ - @SystemApi public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string"; @@ -2361,11 +2374,38 @@ public class CarrierConfigManager { public static final String KEY_CALL_WAITING_SERVICE_CLASS_INT = "call_waiting_service_class_int"; + /** + * This configuration allow the system UI to display different 5G icon for different 5G status. + * + * There are four 5G status: + * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using + * millimeter wave. + * 2. connected: device currently connected to 5G cell as the secondary cell but not using + * millimeter wave. + * 3. not_restricted: device camped on a network that has 5G capability(not necessary to connect + * a 5G cell as a secondary cell) and the use of 5G is not restricted. + * 4. restricted: device camped on a network that has 5G capability(not necessary to connect a + * 5G cell as a secondary cell) but the use of 5G is restricted. + * + * The configured string contains multiple key-value pairs separated by comma. For each pair, + * the key and value is separated by a colon. The key is corresponded to a 5G status above and + * the value is the icon name. Use "None" as the icon name if no icon should be shown in a + * specific 5G status. + * + * Here is an example of the configuration: + * "connected_mmwave:5GPlus,connected:5G,not_restricted:None,restricted:None" + * + * @hide + */ + public static final String KEY_5G_ICON_CONFIGURATION_STRING = + "5g_icon_configuration_string"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; static { sDefaults = new PersistableBundle(); + sDefaults.putString(KEY_CARRIER_CONFIG_VERSION_STRING, ""); sDefaults.putBoolean(KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL, false); sDefaults.putBoolean(KEY_ALWAYS_PLAY_REMOTE_HOLD_TONE_BOOL, false); @@ -2722,6 +2762,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_USE_CALL_FORWARDING_USSD_BOOL, false); sDefaults.putBoolean(KEY_USE_CALLER_ID_USSD_BOOL, false); sDefaults.putInt(KEY_CALL_WAITING_SERVICE_CLASS_INT, 1 /* SERVICE_CLASS_VOICE */); + sDefaults.putString(KEY_5G_ICON_CONFIGURATION_STRING, + "connected_mmwave:None,connected:5G,not_restricted:None,restricted:None"); } /** diff --git a/telephony/java/android/telephony/PhoneNumberRange.java b/telephony/java/android/telephony/PhoneNumberRange.java index d65156fd3ca2..dba803b2bc19 100644 --- a/telephony/java/android/telephony/PhoneNumberRange.java +++ b/telephony/java/android/telephony/PhoneNumberRange.java @@ -71,10 +71,10 @@ public final class PhoneNumberRange implements Parcelable { public PhoneNumberRange(@NonNull String countryCode, @NonNull String prefix, @NonNull String lowerBound, @NonNull String upperBound) { validateLowerAndUpperBounds(lowerBound, upperBound); - if (!Pattern.matches("[0-9]+", countryCode)) { + if (!Pattern.matches("[0-9]*", countryCode)) { throw new IllegalArgumentException("Country code must be all numeric"); } - if (!Pattern.matches("[0-9]+", prefix)) { + if (!Pattern.matches("[0-9]*", prefix)) { throw new IllegalArgumentException("Prefix must be all numeric"); } mCountryCode = countryCode; @@ -133,10 +133,10 @@ public final class PhoneNumberRange implements Parcelable { if (lowerBound.length() != upperBound.length()) { throw new IllegalArgumentException("Lower and upper bounds must have the same length"); } - if (!Pattern.matches("[0-9]+", lowerBound)) { + if (!Pattern.matches("[0-9]*", lowerBound)) { throw new IllegalArgumentException("Lower bound must be all numeric"); } - if (!Pattern.matches("[0-9]+", upperBound)) { + if (!Pattern.matches("[0-9]*", upperBound)) { throw new IllegalArgumentException("Upper bound must be all numeric"); } if (Integer.parseInt(lowerBound) > Integer.parseInt(upperBound)) { diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index f6e8d3422eca..c95837e1e1de 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -1738,7 +1738,10 @@ public class PhoneNumberUtils { * @param number the number to look up. * @return true if the number is in the list of emergency numbers * listed in the RIL / SIM, otherwise return false. + * + * @deprecated Please use {@link TelephonyManager#isCurrentEmergencyNumber(String)} instead. */ + @Deprecated public static boolean isEmergencyNumber(String number) { return isEmergencyNumber(getDefaultVoiceSubId(), number); } @@ -1751,8 +1754,13 @@ public class PhoneNumberUtils { * @param number the number to look up. * @return true if the number is in the list of emergency numbers * listed in the RIL / SIM, otherwise return false. + * + * @deprecated Please use {@link TelephonyManager#isCurrentEmergencyNumber(String)} + * instead. + * * @hide */ + @Deprecated @UnsupportedAppUsage public static boolean isEmergencyNumber(int subId, String number) { // Return true only if the specified number *exactly* matches @@ -1778,8 +1786,12 @@ public class PhoneNumberUtils { * listed in the RIL / SIM, *or* if the number starts with the * same digits as any of those emergency numbers. * + * @deprecated Please use {@link TelephonyManager#isCurrentPotentialEmergencyNumber(String)} + * instead. + * * @hide */ + @Deprecated public static boolean isPotentialEmergencyNumber(String number) { return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number); } @@ -1802,9 +1814,14 @@ public class PhoneNumberUtils { * @return true if the number is in the list of emergency numbers * listed in the RIL / SIM, *or* if the number starts with the * same digits as any of those emergency numbers. + * + * @deprecated Please use {@link TelephonyManager#isCurrentPotentialEmergencyNumber(String)} + * instead. + * * @hide */ @UnsupportedAppUsage + @Deprecated public static boolean isPotentialEmergencyNumber(int subId, String number) { // Check against the emergency numbers listed by the RIL / SIM, // and *don't* require an exact match. @@ -1867,8 +1884,12 @@ public class PhoneNumberUtils { * @return if the number is an emergency number for the specific country, then return true, * otherwise false * + * @deprecated Please use {@link TelephonyManager#isCurrentEmergencyNumber(String)} + * instead. + * * @hide */ + @Deprecated @UnsupportedAppUsage public static boolean isEmergencyNumber(String number, String defaultCountryIso) { return isEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso); @@ -1882,8 +1903,13 @@ public class PhoneNumberUtils { * @param defaultCountryIso the specific country which the number should be checked against * @return if the number is an emergency number for the specific country, then return true, * otherwise false + * + * @deprecated Please use {@link TelephonyManager#isCurrentEmergencyNumber(String)} + * instead. + * * @hide */ + @Deprecated public static boolean isEmergencyNumber(int subId, String number, String defaultCountryIso) { return isEmergencyNumberInternal(subId, number, defaultCountryIso, @@ -1909,8 +1935,12 @@ public class PhoneNumberUtils { * country, *or* if the number starts with the same digits as * any of those emergency numbers. * + * @deprecated Please use {@link TelephonyManager#isCurrentPotentialEmergencyNumber(String)} + * instead. + * * @hide */ + @Deprecated public static boolean isPotentialEmergencyNumber(String number, String defaultCountryIso) { return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso); } @@ -1934,8 +1964,13 @@ public class PhoneNumberUtils { * @return true if the number is an emergency number for the specific * country, *or* if the number starts with the same digits as * any of those emergency numbers. + * + * @deprecated Please use {@link TelephonyManager#isCurrentPotentialEmergencyNumber(String)} + * instead. + * * @hide */ + @Deprecated public static boolean isPotentialEmergencyNumber(int subId, String number, String defaultCountryIso) { return isEmergencyNumberInternal(subId, number, @@ -1983,92 +2018,7 @@ public class PhoneNumberUtils { private static boolean isEmergencyNumberInternal(int subId, String number, String defaultCountryIso, boolean useExactMatch) { - // If the number passed in is null, just return false: - if (number == null) return false; - - // If the number passed in is a SIP address, return false, since the - // concept of "emergency numbers" is only meaningful for calls placed - // over the cell network. - // (Be sure to do this check *before* calling extractNetworkPortionAlt(), - // since the whole point of extractNetworkPortionAlt() is to filter out - // any non-dialable characters (which would turn 'abc911def@example.com' - // into '911', for example.)) - if (isUriNumber(number)) { - return false; - } - - // Strip the separators from the number before comparing it - // to the list. - number = extractNetworkPortionAlt(number); - - String emergencyNumbers = ""; - int slotId = SubscriptionManager.getSlotIndex(subId); - - // retrieve the list of emergency numbers - // check read-write ecclist property first - String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId); - - emergencyNumbers = SystemProperties.get(ecclist, ""); - - Rlog.d(LOG_TAG, "slotId:" + slotId + " subId:" + subId + " country:" - + defaultCountryIso + " emergencyNumbers: " + emergencyNumbers); - - if (TextUtils.isEmpty(emergencyNumbers)) { - // then read-only ecclist property since old RIL only uses this - emergencyNumbers = SystemProperties.get("ro.ril.ecclist"); - } - - if (!TextUtils.isEmpty(emergencyNumbers)) { - // searches through the comma-separated list for a match, - // return true if one is found. - for (String emergencyNum : emergencyNumbers.split(",")) { - // It is not possible to append additional digits to an emergency number to dial - // the number in Brazil - it won't connect. - if (useExactMatch || "BR".equalsIgnoreCase(defaultCountryIso)) { - if (number.equals(emergencyNum)) { - return true; - } - } else { - if (number.startsWith(emergencyNum)) { - return true; - } - } - } - // no matches found against the list! - return false; - } - - Rlog.d(LOG_TAG, "System property doesn't provide any emergency numbers." - + " Use embedded logic for determining ones."); - - // If slot id is invalid, means that there is no sim card. - // According spec 3GPP TS22.101, the following numbers should be - // ECC numbers when SIM/USIM is not present. - emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911"); - - for (String emergencyNum : emergencyNumbers.split(",")) { - if (useExactMatch) { - if (number.equals(emergencyNum)) { - return true; - } - } else { - if (number.startsWith(emergencyNum)) { - return true; - } - } - } - - // No ecclist system property, so use our own list. - if (defaultCountryIso != null) { - ShortNumberInfo info = ShortNumberInfo.getInstance(); - if (useExactMatch) { - return info.isEmergencyNumber(number, defaultCountryIso); - } else { - return info.connectsToEmergencyNumber(number, defaultCountryIso); - } - } - - return false; + return TelephonyManager.getDefault().isCurrentEmergencyNumber(number); } /** @@ -2078,7 +2028,11 @@ public class PhoneNumberUtils { * @param context the specific context which the number should be checked against * @return true if the specified number is an emergency number for the country the user * is currently in. + * + * @deprecated Please use {@link TelephonyManager#isCurrentEmergencyNumber(String)} + * instead. */ + @Deprecated public static boolean isLocalEmergencyNumber(Context context, String number) { return isLocalEmergencyNumber(context, getDefaultVoiceSubId(), number); } @@ -2091,8 +2045,13 @@ public class PhoneNumberUtils { * @param context the specific context which the number should be checked against * @return true if the specified number is an emergency number for the country the user * is currently in. + * + * @deprecated Please use {@link TelephonyManager#isCurrentEmergencyNumber(String)} + * instead. + * * @hide */ + @Deprecated @UnsupportedAppUsage public static boolean isLocalEmergencyNumber(Context context, int subId, String number) { return isLocalEmergencyNumberInternal(subId, number, @@ -2120,8 +2079,13 @@ public class PhoneNumberUtils { * CountryDetector. * * @see android.location.CountryDetector + * + * @deprecated Please use {@link TelephonyManager#isCurrentPotentialEmergencyNumber(String)} + * instead. + * * @hide */ + @Deprecated @UnsupportedAppUsage public static boolean isPotentialLocalEmergencyNumber(Context context, String number) { return isPotentialLocalEmergencyNumber(context, getDefaultVoiceSubId(), number); @@ -2147,9 +2111,13 @@ public class PhoneNumberUtils { * @return true if the specified number is an emergency number for a local country, based on the * CountryDetector. * + * @deprecated Please use {@link TelephonyManager#isCurrentPotentialEmergencyNumber(String)} + * instead. + * * @hide */ @UnsupportedAppUsage + @Deprecated public static boolean isPotentialLocalEmergencyNumber(Context context, int subId, String number) { return isLocalEmergencyNumberInternal(subId, number, @@ -2217,6 +2185,101 @@ public class PhoneNumberUtils { } /** + * Back-up old logics for {@link #isEmergencyNumberInternal} for legacy and deprecate purpose. + * + * @hide + */ + public static boolean isEmergencyNumberInternal(String number, boolean useExactMatch, + String defaultCountryIso) { + // If the number passed in is null, just return false: + if (number == null) return false; + + // If the number passed in is a SIP address, return false, since the + // concept of "emergency numbers" is only meaningful for calls placed + // over the cell network. + // (Be sure to do this check *before* calling extractNetworkPortionAlt(), + // since the whole point of extractNetworkPortionAlt() is to filter out + // any non-dialable characters (which would turn 'abc911def@example.com' + // into '911', for example.)) + if (PhoneNumberUtils.isUriNumber(number)) { + return false; + } + + // Strip the separators from the number before comparing it + // to the list. + number = PhoneNumberUtils.extractNetworkPortionAlt(number); + + String emergencyNumbers = ""; + int slotId = SubscriptionManager.getSlotIndex(getDefaultVoiceSubId()); + + // retrieve the list of emergency numbers + // check read-write ecclist property first + String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId); + + emergencyNumbers = SystemProperties.get(ecclist, ""); + + Rlog.d(LOG_TAG, "slotId:" + slotId + " country:" + + defaultCountryIso + " emergencyNumbers: " + emergencyNumbers); + + if (TextUtils.isEmpty(emergencyNumbers)) { + // then read-only ecclist property since old RIL only uses this + emergencyNumbers = SystemProperties.get("ro.ril.ecclist"); + } + + if (!TextUtils.isEmpty(emergencyNumbers)) { + // searches through the comma-separated list for a match, + // return true if one is found. + for (String emergencyNum : emergencyNumbers.split(",")) { + // It is not possible to append additional digits to an emergency number to dial + // the number in Brazil - it won't connect. + if (useExactMatch || "BR".equalsIgnoreCase(defaultCountryIso)) { + if (number.equals(emergencyNum)) { + return true; + } + } else { + if (number.startsWith(emergencyNum)) { + return true; + } + } + } + // no matches found against the list! + return false; + } + + Rlog.d(LOG_TAG, "System property doesn't provide any emergency numbers." + + " Use embedded logic for determining ones."); + + // If slot id is invalid, means that there is no sim card. + // According spec 3GPP TS22.101, the following numbers should be + // ECC numbers when SIM/USIM is not present. + emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911"); + + for (String emergencyNum : emergencyNumbers.split(",")) { + if (useExactMatch) { + if (number.equals(emergencyNum)) { + return true; + } + } else { + if (number.startsWith(emergencyNum)) { + return true; + } + } + } + + // No ecclist system property, so use our own list. + if (defaultCountryIso != null) { + ShortNumberInfo info = ShortNumberInfo.getInstance(); + if (useExactMatch) { + return info.isEmergencyNumber(number, defaultCountryIso); + } else { + return info.connectsToEmergencyNumber(number, defaultCountryIso); + } + } + + return false; + } + + /** * isVoiceMailNumber: checks a given number against the voicemail * number provided by the RIL and SIM card. The caller must have * the READ_PHONE_STATE credential. diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index e8a28cac3140..0df0dafbe1dd 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -27,12 +27,14 @@ import android.os.Bundle; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; +import android.telephony.emergency.EmergencyNumber; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IPhoneStateListener; import java.lang.ref.WeakReference; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; /** @@ -313,6 +315,8 @@ public class PhoneStateListener { * * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @see #onEmergencyNumberListChanged */ public static final int LISTEN_EMERGENCY_NUMBER_LIST = 0x01000000; @@ -603,6 +607,21 @@ public class PhoneStateListener { } /** + * Callback invoked when the current emergency number list has changed + * + * @param emergencyNumberList Map including the key as the active subscription ID + * (Note: if there is no active subscription, the key is + * {@link SubscriptionManager#getDefaultSubscriptionId}) + * and the value as the list of {@link EmergencyNumber}; + * null if this information is not available. + * @hide + */ + public void onEmergencyNumberListChanged( + @NonNull Map<Integer, List<EmergencyNumber>> emergencyNumberList) { + // default implementation empty + } + + /** * Callback invoked when OEM hook raw event is received. Requires * the READ_PRIVILEGED_PHONE_STATE permission. * @param rawData is the byte array of the OEM hook raw data. @@ -859,6 +878,16 @@ public class PhoneStateListener { () -> psl.onPhysicalChannelConfigurationChanged(configs))); } + @Override + public void onEmergencyNumberListChanged(Map emergencyNumberList) { + PhoneStateListener psl = mPhoneStateListenerWeakRef.get(); + if (psl == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> psl.onEmergencyNumberListChanged(emergencyNumberList))); + } + public void onPhoneCapabilityChanged(PhoneCapability capability) { PhoneStateListener psl = mPhoneStateListenerWeakRef.get(); if (psl == null) return; diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index ca0c854a1a75..78f05168198f 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -27,6 +27,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.NetworkRegistrationState.Domain; +import android.telephony.NetworkRegistrationState.NRStatus; import android.text.TextUtils; import java.lang.annotation.Retention; @@ -1358,6 +1359,18 @@ public class ServiceState implements Parcelable { } /** + * Get the NR 5G status of the mobile data network. + * @return the NR 5G status. + * @hide + */ + public @NRStatus int getNrStatus() { + final NetworkRegistrationState regState = getNetworkRegistrationState( + NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WWAN); + if (regState == null) return NetworkRegistrationState.NR_STATUS_NONE; + return regState.getNrStatus(); + } + + /** * @param nrFrequencyRange the frequency range of 5G NR. * @hide */ @@ -1410,47 +1423,49 @@ public class ServiceState implements Parcelable { } /** @hide */ - public static int rilRadioTechnologyToNetworkType(@RilRadioTechnology int rt) { - switch(rt) { - case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS: - return TelephonyManager.NETWORK_TYPE_GPRS; - case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE: - return TelephonyManager.NETWORK_TYPE_EDGE; - case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS: - return TelephonyManager.NETWORK_TYPE_UMTS; - case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA: - return TelephonyManager.NETWORK_TYPE_HSDPA; - case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA: - return TelephonyManager.NETWORK_TYPE_HSUPA; - case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA: - return TelephonyManager.NETWORK_TYPE_HSPA; - case ServiceState.RIL_RADIO_TECHNOLOGY_IS95A: - case ServiceState.RIL_RADIO_TECHNOLOGY_IS95B: - return TelephonyManager.NETWORK_TYPE_CDMA; - case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT: - return TelephonyManager.NETWORK_TYPE_1xRTT; - case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0: - return TelephonyManager.NETWORK_TYPE_EVDO_0; - case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A: - return TelephonyManager.NETWORK_TYPE_EVDO_A; - case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B: - return TelephonyManager.NETWORK_TYPE_EVDO_B; - case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD: - return TelephonyManager.NETWORK_TYPE_EHRPD; - case ServiceState.RIL_RADIO_TECHNOLOGY_LTE: - return TelephonyManager.NETWORK_TYPE_LTE; - case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP: - return TelephonyManager.NETWORK_TYPE_HSPAP; - case ServiceState.RIL_RADIO_TECHNOLOGY_GSM: - return TelephonyManager.NETWORK_TYPE_GSM; - case ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA: - return TelephonyManager.NETWORK_TYPE_TD_SCDMA; - case ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN: - return TelephonyManager.NETWORK_TYPE_IWLAN; - case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA: - return TelephonyManager.NETWORK_TYPE_LTE_CA; - default: - return TelephonyManager.NETWORK_TYPE_UNKNOWN; + public static int rilRadioTechnologyToNetworkType(@RilRadioTechnology int rat) { + switch(rat) { + case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS: + return TelephonyManager.NETWORK_TYPE_GPRS; + case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE: + return TelephonyManager.NETWORK_TYPE_EDGE; + case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS: + return TelephonyManager.NETWORK_TYPE_UMTS; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA: + return TelephonyManager.NETWORK_TYPE_HSDPA; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA: + return TelephonyManager.NETWORK_TYPE_HSUPA; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA: + return TelephonyManager.NETWORK_TYPE_HSPA; + case ServiceState.RIL_RADIO_TECHNOLOGY_IS95A: + case ServiceState.RIL_RADIO_TECHNOLOGY_IS95B: + return TelephonyManager.NETWORK_TYPE_CDMA; + case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT: + return TelephonyManager.NETWORK_TYPE_1xRTT; + case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0: + return TelephonyManager.NETWORK_TYPE_EVDO_0; + case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A: + return TelephonyManager.NETWORK_TYPE_EVDO_A; + case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B: + return TelephonyManager.NETWORK_TYPE_EVDO_B; + case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD: + return TelephonyManager.NETWORK_TYPE_EHRPD; + case ServiceState.RIL_RADIO_TECHNOLOGY_LTE: + return TelephonyManager.NETWORK_TYPE_LTE; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP: + return TelephonyManager.NETWORK_TYPE_HSPAP; + case ServiceState.RIL_RADIO_TECHNOLOGY_GSM: + return TelephonyManager.NETWORK_TYPE_GSM; + case ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA: + return TelephonyManager.NETWORK_TYPE_TD_SCDMA; + case ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN: + return TelephonyManager.NETWORK_TYPE_IWLAN; + case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA: + return TelephonyManager.NETWORK_TYPE_LTE_CA; + case ServiceState.RIL_RADIO_TECHNOLOGY_NR: + return TelephonyManager.NETWORK_TYPE_NR; + default: + return TelephonyManager.NETWORK_TYPE_UNKNOWN; } } @@ -1531,7 +1546,6 @@ public class ServiceState implements Parcelable { } } - /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public @TelephonyManager.NetworkType int getDataNetworkType() { diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index ea408bf8ff59..1b37bad65d57 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -365,7 +365,6 @@ public final class SmsManager { * * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent) */ - @SystemApi @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(allOf = { android.Manifest.permission.MODIFY_PHONE_STATE, diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 2c06c4720650..c40eb9ac91e3 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -19,6 +19,7 @@ package android.telephony; import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED; import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED; +import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.DurationMillisLong; import android.annotation.NonNull; @@ -52,6 +53,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.telephony.euicc.EuiccManager; import android.telephony.ims.ImsMmTelManager; +import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; @@ -67,6 +69,7 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * SubscriptionManager is the application interface to SubscriptionController @@ -633,7 +636,6 @@ public class SubscriptionManager { * the user is interested in. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - @SystemApi public static final String ACTION_MANAGE_SUBSCRIPTION_PLANS = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS"; @@ -653,7 +655,6 @@ public class SubscriptionManager { * {@code android.permission.MANAGE_SUBSCRIPTION_PLANS} permission. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @SystemApi public static final String ACTION_REFRESH_SUBSCRIPTION_PLANS = "android.telephony.action.REFRESH_SUBSCRIPTION_PLANS"; @@ -2059,7 +2060,6 @@ public class SubscriptionManager { * @throws SecurityException if the caller doesn't meet the requirements * outlined above. */ - @SystemApi public @NonNull List<SubscriptionPlan> getSubscriptionPlans(int subId) { try { SubscriptionPlan[] subscriptionPlans = @@ -2091,7 +2091,6 @@ public class SubscriptionManager { * @throws SecurityException if the caller doesn't meet the requirements * outlined above. */ - @SystemApi public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) { try { getNetworkPolicy().setSubscriptionPlans(subId, @@ -2133,7 +2132,6 @@ public class SubscriptionManager { * @throws SecurityException if the caller doesn't meet the requirements * outlined above. */ - @SystemApi public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered, @DurationMillisLong long timeoutMillis) { try { @@ -2169,7 +2167,6 @@ public class SubscriptionManager { * @throws SecurityException if the caller doesn't meet the requirements * outlined above. */ - @SystemApi public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested, @DurationMillisLong long timeoutMillis) { try { @@ -2405,16 +2402,21 @@ public class SubscriptionManager { * together, some of them may be invisible to the users, etc. * * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} - * permission or can manage all subscriptions in the list, according to their - * acess rules. + * permission or had carrier privilege permission on the subscriptions: + * {@link TelephonyManager#hasCarrierPrivileges(int)} or + * {@link #canManageSubscription(SubscriptionInfo)} + * + * @throws SecurityException if the caller doesn't meet the requirements + * outlined above. * * @param subIdList list of subId that will be in the same group * @return groupUUID a UUID assigned to the subscription group. It returns * null if fails. * */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public String setSubscriptionGroup(int[] subIdList) { + public @Nullable String setSubscriptionGroup(@NonNull int[] subIdList) { String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; if (VDBG) { logd("[setSubscriptionGroup]+ subIdList:" + Arrays.toString(subIdList)); @@ -2434,6 +2436,80 @@ public class SubscriptionManager { } /** + * Remove a list of subscriptions from their subscription group. + * See {@link #setSubscriptionGroup(int[])} for more details. + * + * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} + * permission or had carrier privilege permission on the subscriptions: + * {@link TelephonyManager#hasCarrierPrivileges(int)} or + * {@link #canManageSubscription(SubscriptionInfo)} + * + * @throws SecurityException if the caller doesn't meet the requirements + * outlined above. + * + * @param subIdList list of subId that need removing from their groups. + * @return whether the operation succeeds. + * + */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public boolean removeSubscriptionsFromGroup(@NonNull int[] subIdList) { + String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; + if (VDBG) { + logd("[removeSubscriptionsFromGroup]+ subIdList:" + Arrays.toString(subIdList)); + } + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + return iSub.removeSubscriptionsFromGroup(subIdList, pkgForDebug); + } + } catch (RemoteException ex) { + // ignore it + } + + return false; + } + + /** + * Get subscriptionInfo list of subscriptions that are in the same group of given subId. + * See {@link #setSubscriptionGroup(int[])} for more details. + * + * Caller will either have {@link android.Manifest.permission#READ_PHONE_STATE} + * permission or had carrier privilege permission on the subscription. + * {@link TelephonyManager#hasCarrierPrivileges(int)} + * + * @throws SecurityException if the caller doesn't meet the requirements + * outlined above. + * + * @param subId of which list of subInfo from the same group will be returned. + * @return list of subscriptionInfo that belong to the same group, including the given + * subscription itself. It will return null if the subscription doesn't exist or it + * doesn't belong to any group. + * + */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public @Nullable List<SubscriptionInfo> getSubscriptionsInGroup(int subId) { + String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; + if (VDBG) { + logd("[getSubscriptionsInGroup]+ subId:" + subId); + } + + List<SubscriptionInfo> result = null; + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.getSubscriptionsInGroup(subId, pkgForDebug); + } + } catch (RemoteException ex) { + // ignore it + } + + return result; + } + + /** * Set metered by simInfo index * * @param isMetered whether it’s a metered subscription. @@ -2448,6 +2524,42 @@ public class SubscriptionManager { (iSub)-> iSub.setMetered(isMetered, subId)); } + /** + * Whether system UI should hide a subscription. If it's a bundled opportunistic + * subscription, it shouldn't show up in anywhere in Settings app, dialer app, + * or status bar. + * + * @param info the subscriptionInfo to check against. + * @return true if this subscription should be hidden. + * + * @hide + */ + public static boolean shouldHideSubscription(SubscriptionInfo info) { + return (info != null && !TextUtils.isEmpty(info.getGroupUuid()) && info.isOpportunistic()); + } + + /** + * Return a list of subscriptions that are available and visible to the user. + * Used by Settings app to show a list of subscriptions for user to pick. + * + * <p> + * Permissions android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE is required + * for getSelectableSubscriptionInfoList to be invoked. + * @return list of user selectable subscriptions. + * + * @hide + */ + public @Nullable List<SubscriptionInfo> getSelectableSubscriptionInfoList() { + List<SubscriptionInfo> availableList = getAvailableSubscriptionInfoList(); + if (availableList == null) { + return null; + } else { + return getAvailableSubscriptionInfoList().stream() + .filter(subInfo -> !shouldHideSubscription(subInfo)) + .collect(Collectors.toList()); + } + } + private interface CallISubMethodHelper { int callMethod(ISub iSub) throws RemoteException; } diff --git a/telephony/java/android/telephony/SubscriptionPlan.java b/telephony/java/android/telephony/SubscriptionPlan.java index e8bbe42e834e..d67169ccd284 100644 --- a/telephony/java/android/telephony/SubscriptionPlan.java +++ b/telephony/java/android/telephony/SubscriptionPlan.java @@ -45,7 +45,6 @@ import java.util.Objects; * @see SubscriptionManager#setSubscriptionPlans(int, java.util.List) * @see SubscriptionManager#getSubscriptionPlans(int) */ -@SystemApi public final class SubscriptionPlan implements Parcelable { /** {@hide} */ @IntDef(prefix = "LIMIT_BEHAVIOR_", value = { diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index fa9b76de2e6b..c0250907f7cd 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -810,6 +810,7 @@ public class TelephonyManager { * @see TelephonyManager#NETWORK_TYPE_LTE * @see TelephonyManager#NETWORK_TYPE_EHRPD * @see TelephonyManager#NETWORK_TYPE_HSPAP + * @see TelephonyManager#NETWORK_TYPE_NR * * <p class="note"> * Retrieve with @@ -1216,81 +1217,79 @@ public class TelephonyManager { "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED"; /** + * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates + * the updated carrier id returned by {@link TelephonyManager#getSimCarrierId()}. + * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or + * the carrier cannot be identified. + */ + public static final String EXTRA_CARRIER_ID = "android.telephony.extra.CARRIER_ID"; + + /** + * An string extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which + * indicates the updated carrier name of the current subscription. + * @see TelephonyManager#getSimCarrierIdName() + * <p>Carrier name is a user-facing name of the carrier id {@link #EXTRA_CARRIER_ID}, + * usually the brand name of the subsidiary (e.g. T-Mobile). + */ + public static final String EXTRA_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME"; + + /** * Broadcast Action: The subscription precise carrier identity has changed. - * Similar like {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED}, this intent will be sent - * on the event of {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED}. However, its possible - * that precise carrier identity changes while - * {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} remains the same e.g, the same - * subscription switches to different IMSI could potentially change its precise carrier id. + * The precise carrier id can be used to further differentiate a carrier by different + * networks, by prepaid v.s.postpaid or even by 4G v.s.3G plan. Each carrier has a unique + * carrier id returned by {@link #getSimCarrierId()} but could have multiple precise carrier id. + * e.g, {@link #getSimCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM, + * while {@link #getSimPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based + * on the current subscription IMSI. For carriers without any fine-grained ids, precise carrier + * id is same as carrier id. + * + * <p>Similar like {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED}, this intent will be + * sent on the event of {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} while its also + * possible to be sent without {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} when + * precise carrier id changes with the same carrier id. + * e.g, the same subscription switches to different IMSI could potentially change its + * precise carrier id while carrier id remains the same. + * @see #getSimPreciseCarrierId() + * @see #getSimCarrierId() * * The intent will have the following extra values: * <ul> * <li>{@link #EXTRA_PRECISE_CARRIER_ID} The up-to-date precise carrier id of the * current subscription. * </li> - * <li>{@link #EXTRA_PRECISE_CARRIER_NAME} The up-to-date carrier name of the current - * subscription. + * <li>{@link #EXTRA_PRECISE_CARRIER_NAME} The up-to-date name of the precise carrier id. * </li> * <li>{@link #EXTRA_SUBSCRIPTION_ID} The subscription id associated with the changed carrier * identity. * </li> * </ul> * <p class="note">This is a protected intent that can only be sent by the system. - * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED"; /** - * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates - * the updated carrier id {@link TelephonyManager#getSimCarrierId()} of - * the current subscription. - * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or - * the carrier cannot be identified. - */ - public static final String EXTRA_CARRIER_ID = "android.telephony.extra.CARRIER_ID"; - - /** - * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates - * the updated mno carrier id of the current subscription. - * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or - * the carrier cannot be identified. - * - *@hide - */ - public static final String EXTRA_MNO_CARRIER_ID = "android.telephony.extra.MNO_CARRIER_ID"; - - /** - * An string extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which - * indicates the updated carrier name of the current subscription. - * {@see TelephonyManager#getSimCarrierIdName()} - * <p>Carrier name is a user-facing name of the carrier id {@link #EXTRA_CARRIER_ID}, - * usually the brand name of the subsidiary (e.g. T-Mobile). - */ - public static final String EXTRA_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME"; - - /** * An int extra used with {@link #ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED} which - * indicates the updated precise carrier id {@link TelephonyManager#getSimPreciseCarrierId()} of - * the current subscription. Note, its possible precise carrier id changes while - * {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} remains the same e.g, when - * subscription switch to different IMSI. + * indicates the updated precise carrier id returned by + * {@link TelephonyManager#getSimPreciseCarrierId()}. Note, its possible precise carrier id + * changes while {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} remains the same + * e.g, when subscription switch to different IMSIs. * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or * the carrier cannot be identified. - * @hide */ public static final String EXTRA_PRECISE_CARRIER_ID = "android.telephony.extra.PRECISE_CARRIER_ID"; /** * An string extra used with {@link #ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED} which - * indicates the updated precise carrier name of the current subscription. - * {@see TelephonyManager#getSimPreciseCarrierIdName()} - * <p>it's a user-facing name of the precise carrier id {@link #EXTRA_PRECISE_CARRIER_ID}, - * @hide + * indicates the updated precise carrier name returned by + * {@link TelephonyManager#getSimPreciseCarrierIdName()}. + * <p>it's a user-facing name of the precise carrier id {@link #EXTRA_PRECISE_CARRIER_ID}, e.g, + * Tracfone-AT&T. */ - public static final String EXTRA_PRECISE_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME"; + public static final String EXTRA_PRECISE_CARRIER_NAME = + "android.telephony.extra.PRECISE_CARRIER_NAME"; /** * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} to indicate the @@ -2328,6 +2327,7 @@ public class TelephonyManager { * @see #NETWORK_TYPE_LTE * @see #NETWORK_TYPE_EHRPD * @see #NETWORK_TYPE_HSPAP + * @see #NETWORK_TYPE_NR * * @hide */ @@ -2379,6 +2379,7 @@ public class TelephonyManager { * @see #NETWORK_TYPE_LTE * @see #NETWORK_TYPE_EHRPD * @see #NETWORK_TYPE_HSPAP + * @see #NETWORK_TYPE_NR */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @@ -2565,6 +2566,8 @@ public class TelephonyManager { return "IWLAN"; case NETWORK_TYPE_LTE_CA: return "LTE_CA"; + case NETWORK_TYPE_NR: + return "NR"; default: return "UNKNOWN"; } @@ -6627,7 +6630,7 @@ public class TelephonyManager { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.getCarrierPrivilegeStatus(mSubId) == + return telephony.getCarrierPrivilegeStatus(subId) == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; } } catch (RemoteException ex) { @@ -8513,7 +8516,7 @@ public class TelephonyManager { /** * Returns carrier id name of the current subscription. - * <p>Carrier id name is a user-facing name of carrier id + * <p>Carrier id name is a user-facing name of carrier id returned by * {@link #getSimCarrierId()}, usually the brand name of the subsidiary * (e.g. T-Mobile). Each carrier could configure multiple {@link #getSimOperatorName() SPN} but * should have a single carrier name. Carrier name is not a canonical identity, @@ -8523,7 +8526,7 @@ public class TelephonyManager { * @return Carrier name of the current subscription. Return {@code null} if the subscription is * unavailable or the carrier cannot be identified. */ - public CharSequence getSimCarrierIdName() { + public @Nullable CharSequence getSimCarrierIdName() { try { ITelephony service = getITelephony(); if (service != null) { @@ -8540,10 +8543,10 @@ public class TelephonyManager { * * <p>The precise carrier id can be used to further differentiate a carrier by different * networks, by prepaid v.s.postpaid or even by 4G v.s.3G plan. Each carrier has a unique - * carrier id {@link #getSimCarrierId()} but can have multiple precise carrier id. e.g, - * {@link #getSimCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM, while - * {@link #getSimPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based on the - * current subscription IMSI. + * carrier id returned by {@link #getSimCarrierId()} but could have multiple precise carrier id. + * e.g, {@link #getSimCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM, + * while {@link #getSimPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based + * on the current subscription IMSI. * * <p>For carriers without any fine-grained carrier ids, return {@link #getSimCarrierId()} * <p>Precise carrier ids are defined in the same way as carrier id @@ -8553,8 +8556,6 @@ public class TelephonyManager { * @return Returns fine-grained carrier id of the current subscription. * Return {@link #UNKNOWN_CARRIER_ID} if the subscription is unavailable or the carrier cannot * be identified. - * - * @hide */ public int getSimPreciseCarrierId() { try { @@ -8570,16 +8571,14 @@ public class TelephonyManager { /** * Similar like {@link #getSimCarrierIdName()}, returns user-facing name of the - * precise carrier id {@link #getSimPreciseCarrierId()} + * precise carrier id returned by {@link #getSimPreciseCarrierId()}. * * <p>The returned name is unlocalized. * * @return user-facing name of the subscription precise carrier id. Return {@code null} if the * subscription is unavailable or the carrier cannot be identified. - * - * @hide */ - public CharSequence getSimPreciseCarrierIdName() { + public @Nullable CharSequence getSimPreciseCarrierIdName() { try { ITelephony service = getITelephony(); if (service != null) { @@ -8592,43 +8591,54 @@ public class TelephonyManager { } /** - * Return a list of certs in hex string from loaded carrier privileges access rules. + * Returns carrier id based on sim MCCMNC (returned by {@link #getSimOperator()}) only. + * This is used for fallback when configurations/logic for exact carrier id + * {@link #getSimCarrierId()} are not found. * - * @return a list of certificate in hex string. return {@code null} if there is no certs - * or privilege rules are not loaded yet. + * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a> + * can be updated out-of-band, its possible a MVNO (Mobile Virtual Network Operator) carrier + * was not fully recognized and assigned to its MNO (Mobile Network Operator) carrier id + * by default. After carrier id table update, a new carrier id was assigned. If apps don't + * take the update with the new id, it might be helpful to always fallback by using carrier + * id based on MCCMNC if there is no match. * - * <p>Requires Permission: - * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} - * @hide + * @return matching carrier id from sim MCCMNC. Return {@link #UNKNOWN_CARRIER_ID} if the + * subscription is unavailable or the carrier cannot be identified. */ - @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public List<String> getCertsFromCarrierPrivilegeAccessRules() { + public int getCarrierIdFromSimMccMnc() { try { ITelephony service = getITelephony(); if (service != null) { - return service.getCertsFromCarrierPrivilegeAccessRules(getSubId()); + return service.getCarrierIdFromMccMnc(getSlotIndex(), getSimOperator(), true); } } catch (RemoteException ex) { // This could happen if binder process crashes. } - return null; + return UNKNOWN_CARRIER_ID; } - /** - * Returns MNO carrier id of the current subscription’s MCCMNC. - * <p>MNO carrier id can be solely identified by subscription mccmnc. This is mainly used - * for MNO fallback when exact carrier id {@link #getSimCarrierId()} - * configurations are not found. - * - * @return MNO carrier id of the current subscription. Return the value same as carrier id - * {@link #getSimCarrierId()}, if MNO carrier id cannot be identified. - * @hide - */ - public int getSimMNOCarrierId() { + /** + * Returns carrier id based on MCCMNC (returned by {@link #getSimOperator()}) only. This is + * used for fallback when configurations/logic for exact carrier id {@link #getSimCarrierId()} + * are not found. + * + * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a> + * can be updated out-of-band, its possible a MVNO (Mobile Virtual Network Operator) carrier + * was not fully recognized and assigned to its MNO (Mobile Network Operator) carrier id + * by default. After carrier id table update, a new carrier id was assigned. If apps don't + * take the update with the new id, it might be helpful to always fallback by using carrier + * id based on MCCMNC if there is no match. + * + * @return matching carrier id from passing MCCMNC. Return {@link #UNKNOWN_CARRIER_ID} if the + * subscription is unavailable or the carrier cannot be identified. + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public int getCarrierIdFromMccMnc(String mccmnc) { try { ITelephony service = getITelephony(); if (service != null) { - return service.getSubscriptionMNOCarrierId(getSubId()); + return service.getCarrierIdFromMccMnc(getSlotIndex(), mccmnc, false); } } catch (RemoteException ex) { // This could happen if binder process crashes. @@ -8636,24 +8646,27 @@ public class TelephonyManager { return UNKNOWN_CARRIER_ID; } - /** - * Returns carrier id based on MCCMNC only. This is for fallback when exact carrier id - * {@link #getSimCarrierId()} configurations are not found - * - * @return matching carrier id from passing mccmnc. - * @hide - */ - @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public int getCarrierIdFromMccMnc(String mccmnc) { + /** + * Return a list of certs in hex string from loaded carrier privileges access rules. + * + * @return a list of certificate in hex string. return {@code null} if there is no certs + * or privilege rules are not loaded yet. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public List<String> getCertsFromCarrierPrivilegeAccessRules() { try { ITelephony service = getITelephony(); if (service != null) { - return service.getCarrierIdFromMccMnc(getSlotIndex(), mccmnc); + return service.getCertsFromCarrierPrivilegeAccessRules(getSubId()); } } catch (RemoteException ex) { // This could happen if binder process crashes. } - return UNKNOWN_CARRIER_ID; + return null; } /** @@ -9449,8 +9462,13 @@ public class TelephonyManager { /** * Get the emergency number list based on current locale, sim, default, modem and network. * - * <p>The emergency number {@link EmergencyNumber} with higher display priority is located at - * the smaller index in the returned list. + * <p>In each returned list, the emergency number {@link EmergencyNumber} coming from higher + * priority sources will be located at the smaller index; the priority order of sources are: + * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING} > + * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_SIM} > + * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_DATABASE} > + * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_DEFAULT} > + * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG} * * <p>The subscriptions which the returned list would be based on, are all the active * subscriptions, no matter which subscription could be used to create TelephonyManager. @@ -9459,8 +9477,9 @@ public class TelephonyManager { * app has carrier privileges (see {@link #hasCarrierPrivileges}). * * @return Map including the key as the active subscription ID (Note: if there is no active - * subscription, the key is {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID}) and the value - * as the list of {@link EmergencyNumber}; null if this information is not available. + * subscription, the key is {@link SubscriptionManager#getDefaultSubscriptionId}) and the value + * as the list of {@link EmergencyNumber}; null if this information is not available; or throw + * a SecurityException if the caller does not have the permission. */ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @Nullable @@ -9481,8 +9500,13 @@ public class TelephonyManager { * Get the per-category emergency number list based on current locale, sim, default, modem * and network. * - * <p>The emergency number {@link EmergencyNumber} with higher display priority is located at - * the smaller index in the returned list. + * <p>In each returned list, the emergency number {@link EmergencyNumber} coming from higher + * priority sources will be located at the smaller index; the priority order of sources are: + * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING} > + * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_SIM} > + * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_DATABASE} > + * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_DEFAULT} > + * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG} * * <p>The subscriptions which the returned list would be based on, are all the active * subscriptions, no matter which subscription could be used to create TelephonyManager. @@ -9503,8 +9527,9 @@ public class TelephonyManager { * <li>{@link EmergencyNumber#EMERGENCY_SERVICE_CATEGORY_AIEC} </li> * </ol> * @return Map including the key as the active subscription ID (Note: if there is no active - * subscription, the key is {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID}) and the value - * as the list of {@link EmergencyNumber}; null if this information is not available. + * subscription, the key is {@link SubscriptionManager#getDefaultSubscriptionId}) and the value + * as the list of {@link EmergencyNumber}; null if this information is not available; or throw + * a SecurityException if the caller does not have the permission. */ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @Nullable @@ -9551,7 +9576,44 @@ public class TelephonyManager { if (telephony == null) { return false; } - return telephony.isCurrentEmergencyNumber(number); + return telephony.isCurrentEmergencyNumber(number, true); + } catch (RemoteException ex) { + Log.e(TAG, "isCurrentEmergencyNumber RemoteException", ex); + } + return false; + } + + /** + * Checks if the supplied number is an emergency number based on current locale, sim, default, + * modem and network. + * + * <p> Specifically, this method will return {@code true} if the specified number is an + * emergency number, *or* if the number simply starts with the same digits as any current + * emergency number. + * + * <p>The subscriptions which the identification would be based on, are all the active + * subscriptions, no matter which subscription could be used to create TelephonyManager. + * + * <p>Requires permission: {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} or + * that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @param number - the number to look up + * @return {@code true} if the given number is an emergency number or it simply starts with + * the same digits of any current emergency number based on current locale, sim, modem and + * network; {@code false} if it is not; or throw an SecurityException if the caller does not + * have the required permission/privileges + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean isCurrentPotentialEmergencyNumber(@NonNull String number) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + return false; + } + return telephony.isCurrentEmergencyNumber(number, false); } catch (RemoteException ex) { Log.e(TAG, "isCurrentEmergencyNumber RemoteException", ex); } diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index a1afc0892ba0..8d148c36f3e7 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -969,7 +969,7 @@ public class ApnSetting implements Parcelable { */ public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("[ApnSettingV5] ") + sb.append("[ApnSettingV6] ") .append(mEntryName) .append(", ").append(mId) .append(", ").append(mOperatorNumeric) @@ -996,6 +996,7 @@ public class ApnSetting implements Parcelable { sb.append(", ").append(mPermanentFailed); sb.append(", ").append(mNetworkTypeBitmask); sb.append(", ").append(mApnSetId); + sb.append(", ").append(mCarrierId); return sb.toString(); } diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index 4af31b5e5346..bef11425b470 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -125,7 +125,6 @@ public class DataServiceCallback { * * @param result The result code. Must be one of the {@link ResultCode}. */ - @SystemApi public void onSetDataProfileComplete(@ResultCode int result) { IDataServiceCallback callback = mCallback.get(); if (callback != null) { diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java index 41f7bd7ade63..fe062d5d974a 100644 --- a/telephony/java/android/telephony/emergency/EmergencyNumber.java +++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java @@ -22,6 +22,7 @@ import android.hardware.radio.V1_4.EmergencyNumberSource; import android.hardware.radio.V1_4.EmergencyServiceCategory; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -150,6 +151,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu @IntDef(flag = true, prefix = { "EMERGENCY_NUMBER_SOURCE_" }, value = { EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING, EMERGENCY_NUMBER_SOURCE_SIM, + EMERGENCY_NUMBER_SOURCE_DATABASE, EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG, EMERGENCY_NUMBER_SOURCE_DEFAULT }) @@ -169,6 +171,10 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu * Reference: 3gpp 22.101, Section 10 - Emergency Calls */ public static final int EMERGENCY_NUMBER_SOURCE_SIM = EmergencyNumberSource.SIM; + /** + * Bit-field which indicates the number is from the platform-maintained database. + */ + public static final int EMERGENCY_NUMBER_SOURCE_DATABASE = 1 << 4; /** Bit-field which indicates the number is from the modem config. */ public static final int EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG = EmergencyNumberSource.MODEM_CONFIG; @@ -187,21 +193,24 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu EMERGENCY_NUMBER_SOURCE_SET = new HashSet<Integer>(); EMERGENCY_NUMBER_SOURCE_SET.add(EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING); EMERGENCY_NUMBER_SOURCE_SET.add(EMERGENCY_NUMBER_SOURCE_SIM); + EMERGENCY_NUMBER_SOURCE_SET.add(EMERGENCY_NUMBER_SOURCE_DATABASE); EMERGENCY_NUMBER_SOURCE_SET.add(EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG); EMERGENCY_NUMBER_SOURCE_SET.add(EMERGENCY_NUMBER_SOURCE_DEFAULT); } private final String mNumber; private final String mCountryIso; + private final String mMnc; private final int mEmergencyServiceCategoryBitmask; private final int mEmergencyNumberSourceBitmask; /** @hide */ public EmergencyNumber(@NonNull String number, @NonNull String countryIso, - int emergencyServiceCategories, + @NonNull String mnc, int emergencyServiceCategories, int emergencyNumberSources) { this.mNumber = number; this.mCountryIso = countryIso; + this.mMnc = mnc; this.mEmergencyServiceCategoryBitmask = emergencyServiceCategories; this.mEmergencyNumberSourceBitmask = emergencyNumberSources; } @@ -210,6 +219,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu public EmergencyNumber(Parcel source) { mNumber = source.readString(); mCountryIso = source.readString(); + mMnc = source.readString(); mEmergencyServiceCategoryBitmask = source.readInt(); mEmergencyNumberSourceBitmask = source.readInt(); } @@ -236,6 +246,15 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu } /** + * Get the Mobile Network Code of the emergency number. + * + * @return the Mobile Network Code of the emergency number. + */ + public String getMnc() { + return mMnc; + } + + /** * Returns the bitmask of emergency service categories of the emergency number. * * @return bitmask of the emergency service categories @@ -338,6 +357,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu public void writeToParcel(Parcel dest, int flags) { dest.writeString(mNumber); dest.writeString(mCountryIso); + dest.writeString(mMnc); dest.writeInt(mEmergencyServiceCategoryBitmask); dest.writeInt(mEmergencyNumberSourceBitmask); } @@ -350,10 +370,10 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu @Override public String toString() { - return "EmergencyNumber = " + "[Number]" + mNumber + " / [CountryIso]" + mCountryIso - + " / [ServiceCategories]" - + Integer.toBinaryString(mEmergencyServiceCategoryBitmask) - + " / [Sources]" + Integer.toBinaryString(mEmergencyNumberSourceBitmask); + return "EmergencyNumber:" + "Number-" + mNumber + "|CountryIso-" + mCountryIso + + "|Mnc-" + mMnc + + "|ServiceCategories-" + Integer.toBinaryString(mEmergencyServiceCategoryBitmask) + + "|Sources-" + Integer.toBinaryString(mEmergencyNumberSourceBitmask); } @Override @@ -373,6 +393,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu * The priority of sources are defined as follows: * EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING > * EMERGENCY_NUMBER_SOURCE_SIM > + * EMERGENCY_NUMBER_SOURCE_DATABASE > * EMERGENCY_NUMBER_SOURCE_DEFAULT > * EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG * @@ -385,7 +406,9 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu if (this.isFromSources(EMERGENCY_NUMBER_SOURCE_SIM)) { score += 1 << 3; } - // TODO add a score if the number comes from Google's emergency number database + if (this.isFromSources(EMERGENCY_NUMBER_SOURCE_DATABASE)) { + score += 1 << 2; + } if (this.isFromSources(EMERGENCY_NUMBER_SOURCE_DEFAULT)) { score += 1 << 1; } @@ -412,14 +435,104 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu < emergencyNumber.getDisplayPriorityScore()) { return 1; } else { - /** - * TODO if both numbers have the same display priority score, the number matches the - * Google's emergency number database has a higher display priority. - */ return 0; } } + /** + * In-place merge same emergency numbers in the emergency number list. + * + * A unique EmergencyNumber has a unique combination of ‘number’, ‘mcc’, 'mnc' and + * 'categories' fields. Multiple Emergency Number Sources should be merged into one bitfield + * for the same EmergencyNumber. + * + * @param emergencyNumberList the emergency number list to process + * + * @hide + */ + public static void mergeSameNumbersInEmergencyNumberList( + List<EmergencyNumber> emergencyNumberList) { + if (emergencyNumberList == null) { + return; + } + Set<EmergencyNumber> mergedEmergencyNumber = new HashSet<>(); + for (int i = 0; i < emergencyNumberList.size(); i++) { + // Skip the check because it was merged. + if (mergedEmergencyNumber.contains(emergencyNumberList.get(i))) { + continue; + } + for (int j = i + 1; j < emergencyNumberList.size(); j++) { + if (isSameEmergencyNumber( + emergencyNumberList.get(i), emergencyNumberList.get(j))) { + Rlog.e(LOG_TAG, "Found unexpected duplicate numbers: " + + emergencyNumberList.get(i) + " vs " + emergencyNumberList.get(j)); + // Set the merged emergency number in the current position + emergencyNumberList.set(i, mergeNumbers( + emergencyNumberList.get(i), emergencyNumberList.get(j))); + // Mark the emergency number has been merged + mergedEmergencyNumber.add(emergencyNumberList.get(j)); + } + } + } + // Remove the marked emergency number in the orignal list + for (int i = 0; i < emergencyNumberList.size(); i++) { + if (mergedEmergencyNumber.contains(emergencyNumberList.get(i))) { + emergencyNumberList.remove(i--); + } + } + } + + /** + * Check if two emergency numbers are the same. + * + * A unique EmergencyNumber has a unique combination of ‘number’, ‘mcc’, 'mnc' and + * 'categories' fields. Multiple Emergency Number Sources should be merged into one bitfield + * for the same EmergencyNumber. + * + * @param first first EmergencyNumber to compare + * @param second second EmergencyNumber to compare + * @return true if they are the same EmergencyNumbers; false otherwise. + * + * @hide + */ + public static boolean isSameEmergencyNumber(@NonNull EmergencyNumber first, + @NonNull EmergencyNumber second) { + if (!first.getNumber().equals(second.getNumber())) { + return false; + } + if (!first.getCountryIso().equals(second.getCountryIso())) { + return false; + } + if (!first.getMnc().equals(second.getMnc())) { + return false; + } + if (first.getEmergencyServiceCategoryBitmask() + != second.getEmergencyServiceCategoryBitmask()) { + return false; + } + return true; + } + + /** + * Get a merged EmergencyNumber for two numbers if they are the same. + * + * @param first first EmergencyNumber to compare + * @param second second EmergencyNumber to compare + * @return a merged EmergencyNumber or null if they are not the same EmergencyNumber + * + * @hide + */ + public static EmergencyNumber mergeNumbers(@NonNull EmergencyNumber first, + @NonNull EmergencyNumber second) { + if (isSameEmergencyNumber(first, second)) { + return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(), + first.getEmergencyServiceCategoryBitmask(), + first.getEmergencyNumberSourceBitmask() + | second.getEmergencyNumberSourceBitmask()); + } + return null; + } + public static final Parcelable.Creator<EmergencyNumber> CREATOR = new Parcelable.Creator<EmergencyNumber>() { @Override diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java index 8fcdb6e90569..f1245959b3a8 100644 --- a/telephony/java/android/telephony/ims/ImsReasonInfo.java +++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java @@ -16,11 +16,15 @@ package android.telephony.ims; +import android.annotation.IntDef; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * This class enables an application to get details on why a method call failed. * @@ -30,158 +34,292 @@ import android.os.Parcelable; public final class ImsReasonInfo implements Parcelable { /** - * Specific code of each types + * The Reason is unspecified. */ public static final int CODE_UNSPECIFIED = 0; + + // LOCAL + + // IMS -> Telephony /** - * LOCAL + * The passed argument is invalid. */ - // IMS -> Telephony - // The passed argument is an invalid public static final int CODE_LOCAL_ILLEGAL_ARGUMENT = 101; - // The operation is invoked in invalid call state + /** + * The operation was invoked while in an invalid call state. + */ public static final int CODE_LOCAL_ILLEGAL_STATE = 102; - // IMS service internal error + /** + * IMS service internal error + */ public static final int CODE_LOCAL_INTERNAL_ERROR = 103; - // IMS service goes down (service connection is lost) + /** + * ImsService has crashed (service connection is lost). + */ public static final int CODE_LOCAL_IMS_SERVICE_DOWN = 106; - // No pending incoming call exists + /** + * No pending incoming call exists + */ public static final int CODE_LOCAL_NO_PENDING_CALL = 107; - // IMS Call ended during conference merge process + /** + * IMS Call ended during conference merge process + */ public static final int CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE = 108; // IMS -> Telephony - // Service unavailable; by power off + /** + * Service unavailable; radio power off + */ public static final int CODE_LOCAL_POWER_OFF = 111; - // Service unavailable; by low battery + /** + * Service unavailable; low battery + */ public static final int CODE_LOCAL_LOW_BATTERY = 112; - // Service unavailable; by out of service (data service state) + /** + * Service unavailable; out of service (data service state) + */ public static final int CODE_LOCAL_NETWORK_NO_SERVICE = 121; - // Service unavailable; by no LTE coverage - // (VoLTE is not supported even though IMS is registered) + /** + * Service unavailable; no LTE coverage + * (VoLTE is not supported even though IMS is registered) + */ public static final int CODE_LOCAL_NETWORK_NO_LTE_COVERAGE = 122; - // Service unavailable; by located in roaming area + /** + * Service unavailable; located in roaming area + */ public static final int CODE_LOCAL_NETWORK_ROAMING = 123; - // Service unavailable; by IP changed + /** + * Service unavailable; IP changed + */ public static final int CODE_LOCAL_NETWORK_IP_CHANGED = 124; - // Service unavailable; other + /** + * Service unavailable; for an unspecified reason + */ public static final int CODE_LOCAL_SERVICE_UNAVAILABLE = 131; - // Service unavailable; IMS connection is lost (IMS is not registered) + /** + * Service unavailable; IMS is not registered + */ public static final int CODE_LOCAL_NOT_REGISTERED = 132; // IMS <-> Telephony - // Max call exceeded + /** + * Maximum number of simultaneous calls exceeded + */ public static final int CODE_LOCAL_CALL_EXCEEDED = 141; // IMS <- Telephony - // Call busy + /** + * The call is busy. + */ public static final int CODE_LOCAL_CALL_BUSY = 142; - // Call decline + /** + * The Call has been declined locally on this device. + */ public static final int CODE_LOCAL_CALL_DECLINE = 143; // IMS -> Telephony - // SRVCC is in progress + /** + * Can not complete call; an SRVCC is in progress. + */ public static final int CODE_LOCAL_CALL_VCC_ON_PROGRESSING = 144; - // Resource reservation is failed (QoS precondition) + /** + * Can not complete call; resource reservation is failed (QoS precondition) + */ public static final int CODE_LOCAL_CALL_RESOURCE_RESERVATION_FAILED = 145; - // Retry CS call; VoLTE service can't be provided by the network or remote end - // Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set + /** + * VoLTE service can't be provided by the network or remote end, retry the call. + * Resolve the extra code provided in (EXTRA_CODE_CALL_RETRY_*) if the below code is set + */ public static final int CODE_LOCAL_CALL_CS_RETRY_REQUIRED = 146; - // Retry VoLTE call; VoLTE service can't be provided by the network temporarily + /** + * VoLTE service can't be provided by the network temporarily, retry the call. + */ public static final int CODE_LOCAL_CALL_VOLTE_RETRY_REQUIRED = 147; - // IMS call is already terminated (in TERMINATED state) + /** + * IMS call is already terminated (in TERMINATED state). + */ public static final int CODE_LOCAL_CALL_TERMINATED = 148; - // Handover not feasible + /** + * Call was disconnected because a handover is not feasible due to network conditions. + */ public static final int CODE_LOCAL_HO_NOT_FEASIBLE = 149; - /** + /* * TIMEOUT (IMS -> Telephony) */ - // 1xx waiting timer is expired after sending INVITE request (MO only) + /** + * 1xx waiting timer is expired after sending INVITE request (MO calls only) + */ public static final int CODE_TIMEOUT_1XX_WAITING = 201; - // User no answer during call setup operation (MO/MT) - // MO : 200 OK to INVITE request is not received, - // MT : No action from user after alerting the call + /** + * User didn't answer during call setup operation (MO/MT) + * MO : 200 OK to INVITE request is not received, + * MT : No action from user after alerting the call + */ public static final int CODE_TIMEOUT_NO_ANSWER = 202; - // User no answer during call update operation (MO/MT) - // MO : 200 OK to re-INVITE request is not received, - // MT : No action from user after alerting the call + /** + * User no answer during call update operation (MO/MT) + * MO : 200 OK to re-INVITE request is not received, + * MT : No action from user after alerting the call + */ public static final int CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE = 203; - //Call was blocked by call barring + /** + * The call was blocked by call barring configuration. + */ public static final int CODE_CALL_BARRED = 240; - //Call failures for FDN + /** + * The operation is restricted to fixed dialing numbers only. + */ public static final int CODE_FDN_BLOCKED = 241; - // Network does not accept the emergency call request because IMEI was used as identification - // and this capability is not supported by the network. + /** + * Network rejected the emergency call request because IMEI was used as identification + * and this capability is not supported by the network. + */ public static final int CODE_IMEI_NOT_ACCEPTED = 243; //STK CC errors + /** + * Stk Call Control modified DIAL request to USSD request. + */ public static final int CODE_DIAL_MODIFIED_TO_USSD = 244; + /** + * Stk Call Control modified DIAL request to SS request. + */ public static final int CODE_DIAL_MODIFIED_TO_SS = 245; + /** + * Stk Call Control modified DIAL request to DIAL with modified data. + */ public static final int CODE_DIAL_MODIFIED_TO_DIAL = 246; + /** + * Stk Call Control modified DIAL request to Video DIAL request. + */ public static final int CODE_DIAL_MODIFIED_TO_DIAL_VIDEO = 247; + /** + * Stk Call Control modified Video DIAL request to DIAL request. + */ public static final int CODE_DIAL_VIDEO_MODIFIED_TO_DIAL = 248; + /** + * Stk Call Control modified Video DIAL request to Video DIAL request. + */ public static final int CODE_DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO = 249; + /** + * Stk Call Control modified Video DIAL request to SS request. + */ public static final int CODE_DIAL_VIDEO_MODIFIED_TO_SS = 250; + /** + * Stk Call Control modified Video DIAL request to USSD request. + */ public static final int CODE_DIAL_VIDEO_MODIFIED_TO_USSD = 251; - /** + /* * STATUSCODE (SIP response code) (IMS -> Telephony) */ // 3xx responses - // SIP request is redirected + /** + * SIP 3xx response: SIP request is redirected + */ public static final int CODE_SIP_REDIRECTED = 321; // 4xx responses - // 400 : Bad Request + /** + * Sip 400 response : Bad Request + */ public static final int CODE_SIP_BAD_REQUEST = 331; - // 403 : Forbidden + /** + * Sip 403 response : Forbidden + */ public static final int CODE_SIP_FORBIDDEN = 332; - // 404 : Not Found + /** + * Sip 404 response : Not Found + */ public static final int CODE_SIP_NOT_FOUND = 333; - // 415 : Unsupported Media Type - // 416 : Unsupported URI Scheme - // 420 : Bad Extension + /** + * Not supported, because of one of the following: + * SIP response 415 : Unsupported Media Type, + * SIP response 416 : Unsupported URI Scheme, + * SIP response 420 : Bad Extension + */ public static final int CODE_SIP_NOT_SUPPORTED = 334; - // 408 : Request Timeout + /** + * SIP response 408 : Request Timeout. + */ public static final int CODE_SIP_REQUEST_TIMEOUT = 335; - // 480 : Temporarily Unavailable + /** + * SIP response 480 : Temporarily Unavailable + */ public static final int CODE_SIP_TEMPRARILY_UNAVAILABLE = 336; - // 484 : Address Incomplete + /** + * SIP response 484 : Address Incomplete + */ public static final int CODE_SIP_BAD_ADDRESS = 337; - // 486 : Busy Here - // 600 : Busy Everywhere + /** + * Returned a busy response, may be one of the following: + * SIP response 486 : Busy Here, + * SIP response 600 : Busy Everywhere + */ public static final int CODE_SIP_BUSY = 338; - // 487 : Request Terminated + /** + * SIP response 487 : Request Terminated + */ public static final int CODE_SIP_REQUEST_CANCELLED = 339; - // 406 : Not Acceptable - // 488 : Not Acceptable Here - // 606 : Not Acceptable + /** + * Received a not acceptable response, will be one of the following: + * SIP response 406 : Not Acceptable + * SIP response 488 : Not Acceptable Here + * SIP response 606 : Not Acceptable + */ public static final int CODE_SIP_NOT_ACCEPTABLE = 340; - // 410 : Gone - // 604 : Does Not Exist Anywhere + /** + * Received a not acceptable response, will be one of the following: + * SIP response 410 : Gone + * SIP response 604 : Does Not Exist Anywhere + */ public static final int CODE_SIP_NOT_REACHABLE = 341; - // Others + /** + * Received another unspecified error SIP response from the client. + */ public static final int CODE_SIP_CLIENT_ERROR = 342; - // 481 Transaction Does Not Exist + /** + * SIP response 481: Transaction Does Not Exist + */ public static final int CODE_SIP_TRANSACTION_DOES_NOT_EXIST = 343; // 5xx responses - // 501 : Server Internal Error + /** + * SIP response 501 : Server Internal Error + */ public static final int CODE_SIP_SERVER_INTERNAL_ERROR = 351; - // 503 : Service Unavailable + /** + * SIP response 503 : Service Unavailable + */ public static final int CODE_SIP_SERVICE_UNAVAILABLE = 352; - // 504 : Server Time-out + /** + * SIP response 504 : Server Time-out + */ public static final int CODE_SIP_SERVER_TIMEOUT = 353; - // Others + /** + * Received an unspecified SIP server error response. + */ public static final int CODE_SIP_SERVER_ERROR = 354; // 6xx responses - // 603 : Decline + /** + * 603 : Decline + */ public static final int CODE_SIP_USER_REJECTED = 361; - // Others + /** + * Unspecified 6xx error. + */ public static final int CODE_SIP_GLOBAL_ERROR = 362; - // Emergency failure + + /** + * Emergency call failed in the modem with a temporary fail cause and should be redialed on this + * slot. + */ public static final int CODE_EMERGENCY_TEMP_FAILURE = 363; + /** + * Emergency call failed in the modem with a permanent fail cause and should not be redialed on + * this slot. If there are any other slots available for emergency calling, try those. + */ public static final int CODE_EMERGENCY_PERM_FAILURE = 364; /** @@ -193,132 +331,188 @@ public final class ImsReasonInfo implements Parcelable { */ public static final int CODE_SIP_USER_MARKED_UNWANTED = 365; - /** + /* * MEDIA (IMS -> Telephony) */ - // Media resource initialization failed + /** + * Media resource initialization failed + */ public static final int CODE_MEDIA_INIT_FAILED = 401; - // RTP timeout (no audio / video traffic in the session) + /** + * RTP timeout (no audio / video traffic in the session) + */ public static final int CODE_MEDIA_NO_DATA = 402; - // Media is not supported; so dropped the call + /** + * Media is not supported; so dropped the call + */ public static final int CODE_MEDIA_NOT_ACCEPTABLE = 403; - // Unknown media related errors + /** + * Unspecified media related error. + */ public static final int CODE_MEDIA_UNSPECIFIED = 404; - /** + /* * USER */ // Telephony -> IMS - // User triggers the call end + /** + * User triggers the call to be terminated. + */ public static final int CODE_USER_TERMINATED = 501; - // No action while an incoming call is ringing + /** + * No action was taken while an incoming call was ringing. + */ public static final int CODE_USER_NOANSWER = 502; - // User ignores an incoming call + /** + * User ignored an incoming call. + */ public static final int CODE_USER_IGNORE = 503; - // User declines an incoming call + /** + * User declined an incoming call. + */ public static final int CODE_USER_DECLINE = 504; - // Device declines/ends a call due to low battery + /** + * Device declined/ended a call due to a low battery condition. + */ public static final int CODE_LOW_BATTERY = 505; - // Device declines call due to blacklisted call ID + /** + * Device declined a call due to a blacklisted caller ID. + */ public static final int CODE_BLACKLISTED_CALL_ID = 506; // IMS -> Telephony - // The call is terminated by the network or remote user - public static final int CODE_USER_TERMINATED_BY_REMOTE = 510; - /** - * Extra codes for the specific code value - * This value can be referred when the code is CODE_LOCAL_CALL_CS_RETRY_REQUIRED. + * The call has been terminated by the network or remote user. */ - // Try to connect CS call; normal - public static final int EXTRA_CODE_CALL_RETRY_NORMAL = 1; - // Try to connect CS call without the notification to user - public static final int EXTRA_CODE_CALL_RETRY_SILENT_REDIAL = 2; - // Try to connect CS call by the settings of the menu - public static final int EXTRA_CODE_CALL_RETRY_BY_SETTINGS = 3; + public static final int CODE_USER_TERMINATED_BY_REMOTE = 510; - /** + /* * UT */ + /** + * UT is currently not supported on this device. + */ public static final int CODE_UT_NOT_SUPPORTED = 801; + /** + * UT services are currently not available on this device. + */ public static final int CODE_UT_SERVICE_UNAVAILABLE = 802; + /** + * The requested UT operation is not allowed. + */ public static final int CODE_UT_OPERATION_NOT_ALLOWED = 803; + /** + * The UT request resulted in a network error. + */ public static final int CODE_UT_NETWORK_ERROR = 804; + /** + * The password entered for UT operations does not match the stored password. + */ public static final int CODE_UT_CB_PASSWORD_MISMATCH = 821; //STK CC errors + /** + * Sim Toolkit Call Control modified the UT operation to a dial command. + */ public static final int CODE_UT_SS_MODIFIED_TO_DIAL = 822; + /** + * Sim Toolkit Call Control modified the UT operation to a USSD command. + */ public static final int CODE_UT_SS_MODIFIED_TO_USSD = 823; + /** + * Sim Toolkit Call Control modified the UT operation to another supplementary service command. + */ public static final int CODE_UT_SS_MODIFIED_TO_SS = 824; + /** + * Sim Toolkit Call Control modified the UT operation to a video call dial command. + */ public static final int CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO = 825; + /**@hide*/ + @IntDef(value = { + CODE_UT_NOT_SUPPORTED, + CODE_UT_SERVICE_UNAVAILABLE, + CODE_UT_OPERATION_NOT_ALLOWED, + CODE_UT_NETWORK_ERROR, + CODE_UT_CB_PASSWORD_MISMATCH, + CODE_UT_SS_MODIFIED_TO_DIAL, + CODE_UT_SS_MODIFIED_TO_USSD, + CODE_UT_SS_MODIFIED_TO_SS, + CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO + }, prefix = "CODE_UT_") + @Retention(RetentionPolicy.SOURCE) + public @interface UtReason {} + /** - * ECBM + * Emergency callback mode is not supported. */ public static final int CODE_ECBM_NOT_SUPPORTED = 901; /** - * Fail code used to indicate that Multi-endpoint is not supported by the Ims framework. + * Fail code used to indicate that Multi-endpoint is not supported by the IMS framework. */ public static final int CODE_MULTIENDPOINT_NOT_SUPPORTED = 902; /** - * Ims Registration error code + * IMS Registration error code */ public static final int CODE_REGISTRATION_ERROR = 1000; - /** + /* * CALL DROP error codes (Call could drop because of many reasons like Network not available, * handover, failed, etc) */ - /** - * CALL DROP error code for the case when a device is ePDG capable and when the user is on an - * active wifi call and at the edge of coverage and there is no qualified LTE network available - * to handover the call to. We get a handover NOT_TRIGERRED message from the modem. This error - * code is received as part of the handover message. - */ - public static final int CODE_CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE = 1100; - - /** - * MT call has ended due to a release from the network - * because the call was answered elsewhere + * MT call has ended due to a release from the network because the call was answered elsewhere. */ public static final int CODE_ANSWERED_ELSEWHERE = 1014; /** - * For MultiEndpoint - Call Pull request has failed + * For MultiEndpoint - Call Pull request has failed. */ public static final int CODE_CALL_PULL_OUT_OF_SYNC = 1015; /** - * For MultiEndpoint - Call has been pulled from primary to secondary + * For MultiEndpoint - Call has been pulled from primary to secondary. */ public static final int CODE_CALL_END_CAUSE_CALL_PULL = 1016; /** - * Supplementary services (HOLD/RESUME) failure error codes. - * Values for Supplemetary services failure - Failed, Cancelled and Re-Invite collision. + * CALL DROP error code for the case when a device is ePDG capable and when the user is on an + * active wifi call and at the edge of coverage and there is no qualified LTE network available + * to handover the call to. We get a handover NOT_TRIGERRED message from the modem. This error + * code is received as part of the handover message. + */ + public static final int CODE_CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE = 1100; + + /** + * Supplementary Services (HOLD/RESUME) - the command failed. */ public static final int CODE_SUPP_SVC_FAILED = 1201; + /** + * Supplementary Services (HOLD/RESUME) - the command was cancelled. + */ public static final int CODE_SUPP_SVC_CANCELLED = 1202; + /** + * Supplementary Services (HOLD/RESUME) - the command resulted in a re-invite collision. + */ public static final int CODE_SUPP_SVC_REINVITE_COLLISION = 1203; /** - * DPD Procedure received no response or send failed + * DPD Procedure received no response or send failed. */ public static final int CODE_IWLAN_DPD_FAILURE = 1300; /** - * Establishment of the ePDG Tunnel Failed + * Establishment of the ePDG Tunnel Failed. */ public static final int CODE_EPDG_TUNNEL_ESTABLISH_FAILURE = 1400; /** - * Re-keying of the ePDG Tunnel Failed; may not always result in teardown + * Re-keying of the ePDG Tunnel Failed; may not always result in teardown. */ public static final int CODE_EPDG_TUNNEL_REKEY_FAILURE = 1401; /** - * Connection to the packet gateway is lost + * Connection to the packet gateway is lost. */ public static final int CODE_EPDG_TUNNEL_LOST_CONNECTION = 1402; @@ -571,8 +765,10 @@ public final class ImsReasonInfo implements Parcelable { */ public static final int CODE_REJECT_ONGOING_CS_CALL = 1621; - /* OEM specific error codes. To be used by OEMs when they don't want to - reveal error code which would be replaced by ERROR_UNSPECIFIED */ + /* + * OEM specific error codes. To be used by OEMs when they don't want to reveal error code which + * would be replaced by ERROR_UNSPECIFIED. + */ public static final int CODE_OEM_CAUSE_1 = 0xf001; public static final int CODE_OEM_CAUSE_2 = 0xf002; public static final int CODE_OEM_CAUSE_3 = 0xf003; @@ -597,6 +793,33 @@ public final class ImsReasonInfo implements Parcelable { = "Forbidden. Not Authorized for Service"; + /* + * Extra codes for the specific code value + * This value can be referred when the code is CODE_LOCAL_CALL_CS_RETRY_REQUIRED. + */ + /** + * An extra that may be populated when the {@link CODE_LOCAL_CALL_CS_RETRY_REQUIRED} result has + * been returned. + * <p> + * Try to connect the call using CS + */ + public static final int EXTRA_CODE_CALL_RETRY_NORMAL = 1; + /** + * An extra that may be populated when the {@link CODE_LOCAL_CALL_CS_RETRY_REQUIRED} result has + * been returned. + * <p> + * Try to connect the call using CS and do not notify the user. + */ + public static final int EXTRA_CODE_CALL_RETRY_SILENT_REDIAL = 2; + /** + * An extra that may be populated when the {@link CODE_LOCAL_CALL_CS_RETRY_REQUIRED} result has + * been returned. + * <p> + * Try to connect the call using CS by using the settings. + */ + public static final int EXTRA_CODE_CALL_RETRY_BY_SETTINGS = 3; + + // For main reason code /** @hide */ @UnsupportedAppUsage @@ -638,29 +861,28 @@ public final class ImsReasonInfo implements Parcelable { } /** - * + * @return an integer representing more information about the completion of an operation. */ public int getCode() { return mCode; } /** - * + * @return an optional OEM specified code that provides extra information. */ public int getExtraCode() { return mExtraCode; } /** - * + * @return an optional OEM specified string that provides extra information about the operation + * result. */ public String getExtraMessage() { return mExtraMessage; } /** - * Returns the string format of {@link ImsReasonInfo} - * * @return the string format of {@link ImsReasonInfo} */ public String toString() { diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java index db5ba4784621..3a82517955ce 100644 --- a/telephony/java/android/telephony/ims/ImsSsData.java +++ b/telephony/java/android/telephony/ims/ImsSsData.java @@ -65,6 +65,17 @@ public final class ImsSsData implements Parcelable { public static final int SS_INCOMING_BARRING_DN = 21; public static final int SS_INCOMING_BARRING_ANONYMOUS = 22; + + /**@hide*/ + @IntDef(flag = true, prefix = {"SS_"}, value = { + SS_ACTIVATION, + SS_DEACTIVATION, + SS_INTERROGATION, + SS_REGISTRATION, + SS_ERASURE}) + @Retention(RetentionPolicy.SOURCE) + public @interface RequestType{} + //Supplementary Service Request Types public static final int SS_ACTIVATION = 0; public static final int SS_DEACTIVATION = 1; @@ -72,6 +83,17 @@ public final class ImsSsData implements Parcelable { public static final int SS_REGISTRATION = 3; public static final int SS_ERASURE = 4; + /**@hide*/ + @IntDef(flag = true, prefix = {"SS_"}, value = { + SS_ALL_TELE_AND_BEARER_SERVICES, + SS_ALL_TELESEVICES, + SS_TELEPHONY, + SS_ALL_DATA_TELESERVICES, + SS_SMS_SERVICES, + SS_ALL_TELESERVICES_EXCEPT_SMS}) + @Retention(RetentionPolicy.SOURCE) + public @interface TeleserviceType{} + // Supplementary Service Teleservice Type public static final int SS_ALL_TELE_AND_BEARER_SERVICES = 0; public static final int SS_ALL_TELESEVICES = 1; @@ -191,21 +213,6 @@ public final class ImsSsData implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface ServiceType{} - /** @hide */ - @IntDef(flag = true, prefix = { "SERVICE_CLASS" }, value = { - SERVICE_CLASS_NONE, - SERVICE_CLASS_VOICE, - SERVICE_CLASS_DATA, - SERVICE_CLASS_FAX, - SERVICE_CLASS_SMS, - SERVICE_CLASS_DATA_CIRCUIT_SYNC, - SERVICE_CLASS_DATA_CIRCUIT_ASYNC, - SERVICE_CLASS_DATA_PACKET_ACCESS, - SERVICE_CLASS_DATA_PAD - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ServiceClass{} - /** * The Service type of this Supplementary service. * @hide @@ -221,7 +228,7 @@ public final class ImsSsData implements Parcelable { * {@link #SS_ERASURE} * @hide */ - public final int requestType; + public final @RequestType int requestType; /** * Supplementary Service teleservice type: @@ -234,14 +241,14 @@ public final class ImsSsData implements Parcelable { * * @hide */ - public final int teleserviceType; + public final @TeleserviceType int teleserviceType; /** * Supplementary Service service class. * * @hide */ - public final @ServiceClass int serviceClass; + public final @ServiceClassFlags int serviceClass; /** * Result of Supplementary Service operation. Valid values are: @@ -285,7 +292,7 @@ public final class ImsSsData implements Parcelable { * @see #build() */ public Builder(@ServiceType int serviceType, int requestType, int teleserviceType, - @ServiceClass int serviceClass, int result) { + @ServiceClassFlags int serviceClass, int result) { mImsSsData = new ImsSsData(serviceType, requestType, teleserviceType, serviceClass, result); } @@ -294,7 +301,7 @@ public final class ImsSsData implements Parcelable { * Set the array of {@link ImsSsInfo}s that are associated with this supplementary service * data. */ - public Builder setSuppServiceInfo(@NonNull ImsSsInfo[] imsSsInfos) { + public @NonNull Builder setSuppServiceInfo(@NonNull ImsSsInfo[] imsSsInfos) { mImsSsData.mImsSsInfo = imsSsInfos; return this; } @@ -303,7 +310,8 @@ public final class ImsSsData implements Parcelable { * Set the array of {@link ImsCallForwardInfo}s that are associated with this supplementary * service data. */ - public Builder setCallForwardingInfo(@NonNull ImsCallForwardInfo[] imsCallForwardInfos) { + public @NonNull Builder setCallForwardingInfo( + @NonNull ImsCallForwardInfo[] imsCallForwardInfos) { mImsSsData.mCfInfo = imsCallForwardInfos; return this; } @@ -311,7 +319,7 @@ public final class ImsSsData implements Parcelable { /** * @return an {@link ImsSsData} containing optional parameters. */ - public ImsSsData build() { + public @NonNull ImsSsData build() { return mImsSsData; } } @@ -337,7 +345,7 @@ public final class ImsSsData implements Parcelable { * success, or ImsReasonInfo code if the result is a failure. */ public ImsSsData(@ServiceType int serviceType, int requestType, int teleserviceType, - @ServiceClass int serviceClass, int result) { + @ServiceClassFlags int serviceClass, int result) { this.serviceType = serviceType; this.requestType = requestType; this.teleserviceType = teleserviceType; @@ -449,14 +457,9 @@ public final class ImsSsData implements Parcelable { } /** - * Supplementary Service request Type: - * {@link #SS_ACTIVATION), - * {@link #SS_DEACTIVATION}, - * {@link #SS_INTERROGATION}, - * {@link #SS_REGISTRATION}, - * {@link #SS_ERASURE} + * Supplementary Service request Type. */ - public int getRequestType() { + public @RequestType int getRequestType() { return requestType; } @@ -468,31 +471,25 @@ public final class ImsSsData implements Parcelable { } /** - * Supplementary Service teleservice type: - * {@link #SS_ALL_TELE_AND_BEARER_SERVICES}, - * {@link #SS_ALL_TELESEVICES}, - * {@link #SS_TELEPHONY}, - * {@link #SS_ALL_DATA_TELESERVICES}, - * {@link #SS_SMS_SERVICES}, - * {@link #SS_ALL_TELESERVICES_EXCEPT_SMS} + * Supplementary Service teleservice type. */ - public int getTeleserviceType() { + public @TeleserviceType int getTeleserviceType() { return teleserviceType; } /** * Supplementary Service service class. */ - public @ServiceClass int getServiceClass() { + public @ServiceClassFlags int getServiceClass() { return serviceClass; } /** * Result of Supplementary Service operation. Valid values are: * {@link #RESULT_SUCCESS} if the result is success, or - * {@link ImsReasonInfo} CODE_* code if the result is a failure. + * {@link ImsReasonInfo.UtReason} code if the result is a failure. */ - public int getResult() { + public @ImsReasonInfo.UtReason int getResult() { return result; } diff --git a/telephony/java/android/telephony/ims/ImsSsInfo.java b/telephony/java/android/telephony/ims/ImsSsInfo.java index 0af6e6298c87..031f9e10175b 100644 --- a/telephony/java/android/telephony/ims/ImsSsInfo.java +++ b/telephony/java/android/telephony/ims/ImsSsInfo.java @@ -82,6 +82,7 @@ public final class ImsSsInfo implements Parcelable { */ public static final int SERVICE_PROVISIONED = 1; + /**@hide*/ @IntDef(value = { CLIR_OUTGOING_DEFAULT, CLIR_OUTGOING_INVOCATION, @@ -141,6 +142,7 @@ public final class ImsSsInfo implements Parcelable { */ public static final int CLIR_STATUS_TEMPORARILY_ALLOWED = 4; + /**@hide*/ @IntDef(value = { CLIR_STATUS_NOT_PROVISIONED, CLIR_STATUS_PROVISIONED_PERMANENT, @@ -184,7 +186,7 @@ public final class ImsSsInfo implements Parcelable { * Set the ICB number for IMS call barring. * @param number The number in E.164 international format. */ - public Builder setIncomingCommunicationBarringNumber(@NonNull String number) { + public @NonNull Builder setIncomingCommunicationBarringNumber(@NonNull String number) { mImsSsInfo.mIcbNum = number; return this; } @@ -192,7 +194,7 @@ public final class ImsSsInfo implements Parcelable { /** * Set the provisioning status for a Supplementary Service interrogation response. */ - public Builder setProvisionStatus(@ServiceProvisionStatus int provisionStatus) { + public @NonNull Builder setProvisionStatus(@ServiceProvisionStatus int provisionStatus) { mImsSsInfo.mProvisionStatus = provisionStatus; return this; } @@ -201,7 +203,7 @@ public final class ImsSsInfo implements Parcelable { * Set the Calling Line Identification Restriction (CLIR) status for a supplementary service * interrogation response. */ - public Builder setClirInterrogationStatus(@ClirInterrogationStatus int status) { + public @NonNull Builder setClirInterrogationStatus(@ClirInterrogationStatus int status) { mImsSsInfo.mClirInterrogationStatus = status; return this; } @@ -209,7 +211,7 @@ public final class ImsSsInfo implements Parcelable { /** * Set the Calling line identification Restriction (CLIR) state for outgoing calls. */ - public Builder setClirOutgoingState(@ClirOutgoingState int state) { + public @NonNull Builder setClirOutgoingState(@ClirOutgoingState int state) { mImsSsInfo.mClirOutgoingState = state; return this; } @@ -217,7 +219,7 @@ public final class ImsSsInfo implements Parcelable { /** * @return a built {@link ImsSsInfo} containing optional the parameters that were set. */ - public ImsSsInfo build() { + public @NonNull ImsSsInfo build() { return mImsSsInfo; } } diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java index 0abe45ce448c..13539b855de2 100644 --- a/telephony/java/com/android/internal/telephony/CallerInfo.java +++ b/telephony/java/com/android/internal/telephony/CallerInfo.java @@ -17,6 +17,7 @@ package com.android.internal.telephony; import android.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; @@ -32,15 +33,15 @@ import android.provider.ContactsContract.PhoneLookup; import android.provider.ContactsContract.RawContacts; import android.telephony.PhoneNumberUtils; import android.telephony.Rlog; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; -import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder; import com.android.i18n.phonenumbers.NumberParseException; import com.android.i18n.phonenumbers.PhoneNumberUtil; import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber; -import android.telephony.SubscriptionManager; +import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder; import java.util.Locale; @@ -112,6 +113,9 @@ public class CallerInfo { public Uri contactRefUri; public String lookupKey; + public ComponentName preferredPhoneAccountComponent; + public String preferredPhoneAccountId; + public long userType; /** @@ -264,6 +268,17 @@ public class CallerInfo { info.contactDisplayPhotoUri = null; } + columnIndex = cursor.getColumnIndex(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME); + if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) { + info.preferredPhoneAccountComponent = + ComponentName.unflattenFromString(cursor.getString(columnIndex)); + } + + columnIndex = cursor.getColumnIndex(Data.PREFERRED_PHONE_ACCOUNT_ID); + if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) { + info.preferredPhoneAccountId = cursor.getString(columnIndex); + } + // look for the custom ringtone, create from the string stored // in the database. // An empty string ("") in the database indicates a silent ringtone, diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl index 79f0635c67f7..78fc0bc487bf 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -25,6 +25,7 @@ import android.telephony.PhoneCapability; import android.telephony.PhysicalChannelConfig; import android.telephony.PreciseCallState; import android.telephony.PreciseDataConnectionState; +import android.telephony.emergency.EmergencyNumber; oneway interface IPhoneStateListener { void onServiceStateChanged(in ServiceState serviceState); @@ -53,5 +54,6 @@ oneway interface IPhoneStateListener { void onPhoneCapabilityChanged(in PhoneCapability capability); void onPreferredDataSubIdChanged(in int subId); void onRadioPowerStateChanged(in int state); + void onEmergencyNumberListChanged(in Map emergencyNumberList); } diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index f9db4b0afd12..65d1a920a324 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -210,6 +210,10 @@ interface ISub { */ List<SubscriptionInfo> getOpportunisticSubscriptions(String callingPackage); + boolean removeSubscriptionsFromGroup(in int[] subIdList, String callingPackage); + + List<SubscriptionInfo> getSubscriptionsInGroup(int subId, String callingPackage); + int getSlotIndex(int subId); int[] getSubId(int slotIndex); diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 399dc5255176..b12e7cc3ec6f 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1337,18 +1337,6 @@ interface ITelephony { String getSubscriptionCarrierName(int subId); /** - * Returns MNO carrier id of the current subscription’s MCCMNC. - * <p>MNO carrier id can be solely identified by subscription mccmnc. This is mainly used - * for MNO fallback when exact carrier id {@link #getSimCarrierId()} - * configurations are not found. - * - * @return MNO carrier id of the current subscription. Return the value same as carrier id - * {@link #getSimCarrierId()}, if MNO carrier id cannot be identified. - * @hide - */ - int getSubscriptionMNOCarrierId(int subId); - - /** * Returns fine-grained carrier id of the current subscription. * * <p>The precise carrier id can be used to further differentiate a carrier by different @@ -1383,10 +1371,13 @@ interface ITelephony { * Returns carrier id based on MCCMNC only. This will return a MNO carrier id used for fallback * check when exact carrier id {@link #getSimCarrierId()} configurations are not found * + * @param isSubscriptionMccMnc. If {@true} it means this is a query for subscription mccmnc + * {@false} otherwise. + * * @return carrier id from passing mccmnc. * @hide */ - int getCarrierIdFromMccMnc(int slotIndex, String mccmnc); + int getCarrierIdFromMccMnc(int slotIndex, String mccmnc, boolean isSubscriptionMccMnc); /** * Action set from carrier signalling broadcast receivers to enable/disable metered apns @@ -1716,7 +1707,7 @@ interface ITelephony { /** * Identify if the number is emergency number, based on all the active subscriptions. */ - boolean isCurrentEmergencyNumber(String number); + boolean isCurrentEmergencyNumber(String number, boolean exactMatch); /** * Return a list of certs in hex string from loaded carrier privileges access rules. diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 76e7509c1094..d9f5c3f6d0fa 100644 --- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -81,5 +81,5 @@ interface ITelephonyRegistry { void notifyPhoneCapabilityChanged(in PhoneCapability capability); void notifyPreferredDataSubIdChanged(int preferredSubId); void notifyRadioPowerStateChanged(in int state); - void notifyEmergencyNumberList(in List<EmergencyNumber> emergencyNumberList); + void notifyEmergencyNumberList(); } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index cb8269efe443..c9343171e03e 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -483,4 +483,5 @@ public interface RILConstants { int RIL_UNSOL_HAL_NON_RIL_BASE = 1100; int RIL_UNSOL_ICC_SLOT_STATUS = 1100; int RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG = 1101; + int RIL_UNSOL_EMERGENCY_NUMBER_LIST = 1102; } diff --git a/test-mock/src/android/test/mock/MockContentProvider.java b/test-mock/src/android/test/mock/MockContentProvider.java index b917fbd8a1fe..0ac35bc2628c 100644 --- a/test-mock/src/android/test/mock/MockContentProvider.java +++ b/test-mock/src/android/test/mock/MockContentProvider.java @@ -54,10 +54,10 @@ public class MockContentProvider extends ContentProvider { */ private class InversionIContentProvider implements IContentProvider { @Override - public ContentProviderResult[] applyBatch(String callingPackage, + public ContentProviderResult[] applyBatch(String callingPackage, String authority, ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException { - return MockContentProvider.this.applyBatch(operations); + return MockContentProvider.this.applyBatch(authority, operations); } @Override @@ -112,9 +112,9 @@ public class MockContentProvider extends ContentProvider { } @Override - public Bundle call(String callingPackage, String method, String request, Bundle args) - throws RemoteException { - return MockContentProvider.this.call(method, request, args); + public Bundle call(String callingPackage, String authority, String method, String request, + Bundle args) throws RemoteException { + return MockContentProvider.this.call(authority, method, request, args); } @Override diff --git a/test-mock/src/android/test/mock/MockIContentProvider.java b/test-mock/src/android/test/mock/MockIContentProvider.java index 112d7eef3dbe..fc2a4644b994 100644 --- a/test-mock/src/android/test/mock/MockIContentProvider.java +++ b/test-mock/src/android/test/mock/MockIContentProvider.java @@ -80,7 +80,7 @@ public class MockIContentProvider implements IContentProvider { } @Override - public ContentProviderResult[] applyBatch(String callingPackage, + public ContentProviderResult[] applyBatch(String callingPackage, String authority, ArrayList<ContentProviderOperation> operations) { throw new UnsupportedOperationException("unimplemented mock method"); } @@ -103,8 +103,8 @@ public class MockIContentProvider implements IContentProvider { } @Override - public Bundle call(String callingPackage, String method, String request, Bundle args) - throws RemoteException { + public Bundle call(String callingPackage, String authority, String method, String request, + Bundle args) throws RemoteException { throw new UnsupportedOperationException("unimplemented mock method"); } diff --git a/tests/FrameworkPerf/AndroidManifest.xml b/tests/FrameworkPerf/AndroidManifest.xml index d62ef9ec210c..ca25386a992c 100644 --- a/tests/FrameworkPerf/AndroidManifest.xml +++ b/tests/FrameworkPerf/AndroidManifest.xml @@ -13,7 +13,8 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> - <service android:name="SchedulerService"> + <service android:name="SchedulerService" + android:foregroundServiceType="sync"> </service> <service android:name="TestService" android:process=":test"> </service> diff --git a/tests/FrameworkPerf/src/com/android/frameworkperf/SchedulerService.java b/tests/FrameworkPerf/src/com/android/frameworkperf/SchedulerService.java index fc3f39027348..d4cbbf9c8271 100644 --- a/tests/FrameworkPerf/src/com/android/frameworkperf/SchedulerService.java +++ b/tests/FrameworkPerf/src/com/android/frameworkperf/SchedulerService.java @@ -17,16 +17,22 @@ package com.android.frameworkperf; import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.IBinder; public class SchedulerService extends Service { + private static final String NOTIFICATION_CHANNEL_ID = SchedulerService.class.getSimpleName(); @Override public int onStartCommand(Intent intent, int flags, int startId) { - Notification status = new Notification.Builder(this) + getSystemService(NotificationManager.class).createNotificationChannel( + new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID, + NotificationManager.IMPORTANCE_DEFAULT)); + Notification status = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID) .setSmallIcon(R.drawable.stat_happy) .setWhen(System.currentTimeMillis()) .setContentTitle("Scheduler Test running") diff --git a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java index be74a6d162ae..7a5e7325ad22 100644 --- a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java +++ b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java @@ -92,11 +92,11 @@ public class UsageStatsDatabasePerfTest { event.mPackage = "fake.package.name" + pkg; event.mClass = event.mPackage + ".class1"; event.mTimeStamp = 1; - event.mEventType = UsageEvents.Event.MOVE_TO_FOREGROUND; + event.mEventType = UsageEvents.Event.ACTIVITY_RESUMED; for (int evt = 0; evt < eventsPerPackage; evt++) { intervalStats.events.insert(event); intervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, - event.mEventType); + event.mEventType, 1); } } } diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java index 3480e96b3547..53afa26796ea 100644 --- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java +++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageLogActivity.java @@ -21,13 +21,14 @@ import android.app.usage.UsageStatsManager; import android.content.Context; import android.os.Bundle; import android.os.Handler; -import androidx.collection.CircularArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; +import androidx.collection.CircularArray; + public class UsageLogActivity extends ListActivity implements Runnable { private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14; @@ -155,10 +156,10 @@ public class UsageLogActivity extends ListActivity implements Runnable { private String eventToString(int eventType) { switch (eventType) { - case UsageEvents.Event.MOVE_TO_FOREGROUND: + case UsageEvents.Event.ACTIVITY_RESUMED: return "Foreground"; - case UsageEvents.Event.MOVE_TO_BACKGROUND: + case UsageEvents.Event.ACTIVITY_PAUSED: return "Background"; case UsageEvents.Event.CONFIGURATION_CHANGE: diff --git a/tests/net/Android.mk b/tests/net/Android.mk index 132135dc89bc..9d1edbf1eaf0 100644 --- a/tests/net/Android.mk +++ b/tests/net/Android.mk @@ -51,7 +51,6 @@ LOCAL_JNI_SHARED_LIBRARIES := \ liblog \ liblzma \ libnativehelper \ - libnetdaidl \ libpackagelistparser \ libpcre2 \ libselinux \ @@ -93,7 +92,6 @@ LOCAL_SHARED_LIBRARIES := \ liblog \ libcutils \ libnativehelper \ - libnetdaidl \ netd_aidl_interface-cpp LOCAL_STATIC_LIBRARIES := \ diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java index b40921ff4f84..84f735985b0d 100644 --- a/tests/net/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java @@ -24,9 +24,9 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; 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_VALIDATED; import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES; @@ -46,7 +46,6 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import android.util.ArraySet; - import org.junit.Test; import org.junit.runner.RunWith; @@ -457,6 +456,69 @@ public class NetworkCapabilitiesTest { assertEquals(nc1, nc2); } + @Test + public void testSetNetworkSpecifierOnMultiTransportNc() { + // Sequence 1: Transport + Transport + NetworkSpecifier + NetworkCapabilities nc1 = new NetworkCapabilities(); + nc1.addTransportType(TRANSPORT_CELLULAR).addTransportType(TRANSPORT_WIFI); + try { + nc1.setNetworkSpecifier(new StringNetworkSpecifier("specs")); + fail("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!"); + } catch (IllegalStateException expected) { + // empty + } + + // Sequence 2: Transport + NetworkSpecifier + Transport + NetworkCapabilities nc2 = new NetworkCapabilities(); + nc2.addTransportType(TRANSPORT_CELLULAR).setNetworkSpecifier( + new StringNetworkSpecifier("specs")); + try { + nc2.addTransportType(TRANSPORT_WIFI); + fail("Cannot set a second TransportType of a network which has a NetworkSpecifier!"); + } catch (IllegalStateException expected) { + // empty + } + } + + @Test + public void testSetTransportInfoOnMultiTransportNc() { + // Sequence 1: Transport + Transport + TransportInfo + NetworkCapabilities nc1 = new NetworkCapabilities(); + nc1.addTransportType(TRANSPORT_CELLULAR).addTransportType(TRANSPORT_WIFI) + .setTransportInfo(new TransportInfo() {}); + + // Sequence 2: Transport + NetworkSpecifier + Transport + NetworkCapabilities nc2 = new NetworkCapabilities(); + nc2.addTransportType(TRANSPORT_CELLULAR).setTransportInfo(new TransportInfo() {}) + .addTransportType(TRANSPORT_WIFI); + } + + @Test + public void testCombineTransportInfo() { + NetworkCapabilities nc1 = new NetworkCapabilities(); + nc1.setTransportInfo(new TransportInfo() { + // empty + }); + NetworkCapabilities nc2 = new NetworkCapabilities(); + // new TransportInfo so that object is not #equals to nc1's TransportInfo (that's where + // combine fails) + nc2.setTransportInfo(new TransportInfo() { + // empty + }); + + try { + nc1.combineCapabilities(nc2); + fail("Should not be able to combine NetworkCabilities which contain TransportInfos"); + } catch (IllegalStateException expected) { + // empty + } + + // verify that can combine with identical TransportInfo objects + NetworkCapabilities nc3 = new NetworkCapabilities(); + nc3.setTransportInfo(nc1.getTransportInfo()); + nc1.combineCapabilities(nc3); + } + private void assertEqualsThroughMarshalling(NetworkCapabilities netCap) { Parcel p = Parcel.obtain(); netCap.writeToParcel(p, /* flags */ 0); diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java index d6dbf5aaa9d8..1e3a49bd8cc8 100644 --- a/tests/net/java/android/net/NetworkStatsTest.java +++ b/tests/net/java/android/net/NetworkStatsTest.java @@ -448,22 +448,58 @@ public class NetworkStatsTest { } @Test - public void testWithoutUid() throws Exception { - final NetworkStats before = new NetworkStats(TEST_START, 3) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 64L, 4L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 512L, 32L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L); - - final NetworkStats after = before.withoutUids(new int[] { 100 }); - assertEquals(6, before.size()); - assertEquals(2, after.size()); - assertValues(after, 0, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L); - assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L); + public void testRemoveUids() throws Exception { + final NetworkStats before = new NetworkStats(TEST_START, 3); + + // Test 0 item stats. + NetworkStats after = before.clone(); + after.removeUids(new int[0]); + assertEquals(0, after.size()); + after.removeUids(new int[] {100}); + assertEquals(0, after.size()); + + // Test 1 item stats. + before.addValues(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L); + after = before.clone(); + after.removeUids(new int[0]); + assertEquals(1, after.size()); + assertValues(after, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L); + after.removeUids(new int[] {99}); + assertEquals(0, after.size()); + + // Append remaining test items. + before.addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L) + .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L) + .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L) + .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L) + .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L) + .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L); + assertEquals(7, before.size()); + + // Test remove with empty uid list. + after = before.clone(); + after.removeUids(new int[0]); + assertValues(after.getTotalIncludingTags(null), 127L, 254L, 0L, 4L, 40L); + + // Test remove uids don't exist in stats. + after.removeUids(new int[] {98, 0, Integer.MIN_VALUE, Integer.MAX_VALUE}); + assertValues(after.getTotalIncludingTags(null), 127L, 254L, 0L, 4L, 40L); + + // Test remove all uids. + after.removeUids(new int[] {99, 100, 100, 101}); + assertEquals(0, after.size()); + + // Test remove in the middle. + after = before.clone(); + after.removeUids(new int[] {100}); + assertEquals(3, after.size()); + assertValues(after, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L); + assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 32L, 4L, 0L, 0L, 0L); + assertValues(after, 2, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 64L, 2L, 0L, 0L, 0L); } @Test diff --git a/tests/net/java/android/net/ip/IpClientTest.java b/tests/net/java/android/net/ip/IpClientTest.java index 5a8d2cd0c5a2..cba3c6572c46 100644 --- a/tests/net/java/android/net/ip/IpClientTest.java +++ b/tests/net/java/android/net/ip/IpClientTest.java @@ -81,7 +81,7 @@ public class IpClientTest { private static final int TEST_IFINDEX = 1001; // See RFC 7042#section-2.1.2 for EUI-48 documentation values. private static final MacAddress TEST_MAC = MacAddress.fromString("00:00:5E:00:53:01"); - private static final int TEST_TIMEOUT_MS = 200; + private static final int TEST_TIMEOUT_MS = 400; @Mock private Context mContext; @Mock private INetworkManagementService mNMService; diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp index 3a71e836aa38..2ef8b999a192 100644 --- a/tools/aapt2/configuration/ConfigurationParser_test.cpp +++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp @@ -230,7 +230,7 @@ TEST_F(ConfigurationParserTest, ValidateFile) { test::ParseConfigOrDie("fr"), test::ParseConfigOrDie("de"))); ASSERT_TRUE(a1.android_sdk); ASSERT_TRUE(a1.android_sdk.value().min_sdk_version); - EXPECT_EQ(a1.android_sdk.value().min_sdk_version, 19l); + EXPECT_EQ(a1.android_sdk.value().min_sdk_version, 19L); EXPECT_THAT(a1.textures, SizeIs(1ul)); EXPECT_THAT(a1.features, SizeIs(1ul)); @@ -250,7 +250,7 @@ TEST_F(ConfigurationParserTest, ValidateFile) { test::ParseConfigOrDie("fr-rCA"))); ASSERT_TRUE(a2.android_sdk); ASSERT_TRUE(a2.android_sdk.value().min_sdk_version); - EXPECT_EQ(a2.android_sdk.value().min_sdk_version, 19l); + EXPECT_EQ(a2.android_sdk.value().min_sdk_version, 19L); EXPECT_THAT(a2.textures, SizeIs(1ul)); EXPECT_THAT(a2.features, SizeIs(1ul)); } diff --git a/tools/hiddenapi/exclude.sh b/tools/hiddenapi/exclude.sh index 2291e5a92730..4ffcf6846947 100755 --- a/tools/hiddenapi/exclude.sh +++ b/tools/hiddenapi/exclude.sh @@ -11,6 +11,7 @@ LIBCORE_PACKAGES="\ android.system \ com.android.bouncycastle \ com.android.conscrypt \ + com.android.i18n.phonenumbers \ com.android.okhttp \ com.sun \ dalvik \ diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py index 9dceba2163eb..6b4c34677d96 100755 --- a/tools/localedata/extract_icu_data.py +++ b/tools/localedata/extract_icu_data.py @@ -155,7 +155,7 @@ def dump_representative_locales(representative_locales): print print 'std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({' for locale in sorted(representative_locales): - print ' 0x%08Xllu, // %s' % ( + print ' 0x%08XLLU, // %s' % ( pack_to_uint64(locale), locale) print '});' diff --git a/tools/processors/view_inspector/Android.bp b/tools/processors/view_inspector/Android.bp new file mode 100644 index 000000000000..ca6b3c4572f5 --- /dev/null +++ b/tools/processors/view_inspector/Android.bp @@ -0,0 +1,27 @@ +java_library_host { + name: "view-inspector-annotation-processor", + + srcs: ["src/java/**/*.java"], + java_resource_dirs: ["src/resources"], + + static_libs: [ + "javapoet", + ], + + use_tools_jar: true, +} + +java_test_host { + name: "view-inspector-annotation-processor-test", + + srcs: ["test/java/**/*.java"], + java_resource_dirs: ["test/resources"], + + static_libs: [ + "guava", + "junit", + "view-inspector-annotation-processor", + ], + + test_suites: ["general-tests"], +} diff --git a/tools/processors/view_inspector/OWNERS b/tools/processors/view_inspector/OWNERS new file mode 100644 index 000000000000..0473f54e57ca --- /dev/null +++ b/tools/processors/view_inspector/OWNERS @@ -0,0 +1,3 @@ +alanv@google.com +ashleyrose@google.com +aurimas@google.com
\ No newline at end of file diff --git a/tools/processors/view_inspector/TEST_MAPPING b/tools/processors/view_inspector/TEST_MAPPING new file mode 100644 index 000000000000..a91b5b452c39 --- /dev/null +++ b/tools/processors/view_inspector/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "view-inspector-annotation-processor-test" + } + ] +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java new file mode 100644 index 000000000000..f157949f4d1b --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2018 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.processor.view.inspector; + +import java.util.Map; +import java.util.Optional; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +/** + * Utilities for working with {@link AnnotationMirror}. + */ +final class AnnotationUtils { + private final Elements mElementUtils; + private final Types mTypeUtils; + + AnnotationUtils(ProcessingEnvironment processingEnv) { + mElementUtils = processingEnv.getElementUtils(); + mTypeUtils = processingEnv.getTypeUtils(); + } + + /** + * Get a {@link AnnotationMirror} specified by name from an {@link Element}. + * + * @param qualifiedName The fully qualified name of the annotation to search for + * @param element The element to search for annotations on + * @return The mirror of the requested annotation + * @throws ProcessingException If there is not exactly one of the requested annotation. + */ + AnnotationMirror exactlyOneMirror(String qualifiedName, Element element) { + final Element targetTypeElment = mElementUtils.getTypeElement(qualifiedName); + final TypeMirror targetType = targetTypeElment.asType(); + AnnotationMirror result = null; + + for (AnnotationMirror annotation : element.getAnnotationMirrors()) { + final TypeMirror annotationType = annotation.getAnnotationType().asElement().asType(); + if (mTypeUtils.isSameType(annotationType, targetType)) { + if (result == null) { + result = annotation; + } else { + final String message = String.format( + "Element had multiple instances of @%s, expected exactly one", + targetTypeElment.getSimpleName()); + + throw new ProcessingException(message, element, annotation); + } + } + } + + if (result == null) { + final String message = String.format( + "Expected an @%s annotation, found none", targetTypeElment.getSimpleName()); + throw new ProcessingException(message, element); + } else { + return result; + } + } + + /** + * Extract a string-valued property from an {@link AnnotationMirror}. + * + * @param propertyName The name of the requested property + * @param annotationMirror The mirror to search for the property + * @return The String value of the annotation or null + */ + Optional<String> stringProperty(String propertyName, AnnotationMirror annotationMirror) { + final AnnotationValue value = valueByName(propertyName, annotationMirror); + if (value != null) { + return Optional.of((String) value.getValue()); + } else { + return Optional.empty(); + } + } + + + /** + * Extract a {@link AnnotationValue} from a mirror by string property name. + * + * @param propertyName The name of the property requested property + * @param annotationMirror + * @return + */ + AnnotationValue valueByName(String propertyName, AnnotationMirror annotationMirror) { + final Map<? extends ExecutableElement, ? extends AnnotationValue> valueMap = + annotationMirror.getElementValues(); + + for (ExecutableElement method : valueMap.keySet()) { + if (method.getSimpleName().contentEquals(propertyName)) { + return valueMap.get(method); + } + } + + return null; + } + +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java new file mode 100644 index 000000000000..579745d2aaef --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2018 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.processor.view.inspector; + +import com.squareup.javapoet.ClassName; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * Model of an inspectable class derived from annotations. + * + * This class does not use any {@code javax.lang.model} objects to facilitate building models for + * testing {@link InspectionCompanionGenerator}. + */ +public final class InspectableClassModel { + private final ClassName mClassName; + private final Map<String, Property> mPropertyMap; + private Optional<String> mNodeName = Optional.empty(); + + /** + * @param className The name of the modeled class + */ + public InspectableClassModel(ClassName className) { + mClassName = className; + mPropertyMap = new HashMap<>(); + } + + public ClassName getClassName() { + return mClassName; + } + + public Optional<String> getNodeName() { + return mNodeName; + } + + public void setNodeName(Optional<String> nodeName) { + mNodeName = nodeName; + } + + /** + * Add a property to the model, replacing an existing property of the same name. + * + * @param property The property to add or replace + */ + public void putProperty(Property property) { + mPropertyMap.put(property.getName(), property); + } + + /** + * Get a property by name. + * + * @param name The name of the property + * @return The property or an empty optional + */ + public Optional<Property> getProperty(String name) { + return Optional.of(mPropertyMap.get(name)); + } + + /** + * Get all the properties defined on this model. + * + * @return An un-ordered collection of properties + */ + public Collection<Property> getAllProperties() { + return mPropertyMap.values(); + } + + /** + * Model an inspectable property + */ + public static final class Property { + private final String mName; + private String mGetter; + private Type mType; + private boolean mAttributeIdInferrableFromR = true; + private int mAttributeId = 0; + + public Property(String name) { + mName = name; + } + + public int getAttributeId() { + return mAttributeId; + } + + /** + * Set the attribute ID, and mark the attribute ID as non-inferrable. + * + * @param attributeId The attribute ID for this property + */ + public void setAttributeId(int attributeId) { + mAttributeIdInferrableFromR = false; + mAttributeId = attributeId; + } + + public boolean isAttributeIdInferrableFromR() { + return mAttributeIdInferrableFromR; + } + + public void setAttributeIdInferrableFromR(boolean attributeIdInferrableFromR) { + mAttributeIdInferrableFromR = attributeIdInferrableFromR; + } + + public String getName() { + return mName; + } + + public String getGetter() { + return mGetter; + } + + public void setGetter(String getter) { + mGetter = getter; + } + + public Type getType() { + return mType; + } + + public void setType(Type type) { + mType = type; + } + + public enum Type { + /** Primitive or boxed {@code boolean} */ + BOOLEAN, + + /** Primitive or boxed {@code byte} */ + BYTE, + + /** Primitive or boxed {@code char} */ + CHAR, + + /** Primitive or boxed {@code double} */ + DOUBLE, + + /** Primitive or boxed {@code float} */ + FLOAT, + + /** Primitive or boxed {@code int} */ + INT, + + /** Primitive or boxed {@code long} */ + LONG, + + /** Primitive or boxed {@code short} */ + SHORT, + + /** Any other object */ + OBJECT, + + /** + * A color object or packed color {@code int} or {@code long}. + * + * @see android.graphics.Color + * @see android.annotation.ColorInt + * @see android.annotation.ColorLong + */ + COLOR, + + /** + * An {@code int} packed with a gravity specification + * + * @see android.view.Gravity + */ + GRAVITY, + + /** + * An enumeration packed into an {@code int}. + * + * @see android.view.inspector.IntEnumMapping + */ + INT_ENUM, + + /** + * Non-exclusive or partially-exclusive flags packed into an {@code int}. + * + * @see android.view.inspector.IntFlagMapping + */ + INT_FLAG + } + } +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableNodeNameProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableNodeNameProcessor.java new file mode 100644 index 000000000000..a186a82af160 --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableNodeNameProcessor.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 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.processor.view.inspector; + +import java.util.Optional; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; + +/** + * Process {InspectableNodeName} annotations + * + * @see android.view.inspector.InspectableNodeName + */ +public final class InspectableNodeNameProcessor implements ModelProcessor { + private final String mQualifiedName; + private final ProcessingEnvironment mProcessingEnv; + private final AnnotationUtils mAnnotationUtils; + + /** + * @param annotationQualifiedName The qualified name of the annotation to process + * @param processingEnv The processing environment from the parent processor + */ + public InspectableNodeNameProcessor( + String annotationQualifiedName, + ProcessingEnvironment processingEnv) { + mQualifiedName = annotationQualifiedName; + mProcessingEnv = processingEnv; + mAnnotationUtils = new AnnotationUtils(processingEnv); + } + + /** + * Set the node name on the model if one is supplied. + * + * If the model already has a different node name, the node name will not be updated, and + * the processor will print an error the the messager. + * + * @param element The annotated element to operate on + * @param model The model this element should be merged into + */ + @Override + public void process(Element element, InspectableClassModel model) { + try { + final AnnotationMirror mirror = + mAnnotationUtils.exactlyOneMirror(mQualifiedName, element); + final Optional<String> nodeName = mAnnotationUtils.stringProperty("value", mirror); + + if (!model.getNodeName().isPresent() || model.getNodeName().equals(nodeName)) { + model.setNodeName(nodeName); + } else { + final String message = String.format( + "Node name was already set to \"%s\", refusing to change it to \"%s\".", + model.getNodeName().get(), + nodeName); + throw new ProcessingException(message, element, mirror); + } + } catch (ProcessingException processingException) { + processingException.print(mProcessingEnv.getMessager()); + } + } +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java new file mode 100644 index 000000000000..3b85dbb77d5c --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2018 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.processor.view.inspector; + +import android.processor.view.inspector.InspectableClassModel.Property; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.NameAllocator; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; + +import javax.annotation.processing.Filer; +import javax.lang.model.element.Modifier; + +/** + * Generates a source file defining a {@link android.view.inspector.InspectionCompanion}. + */ +public final class InspectionCompanionGenerator { + private final Filer mFiler; + private final Class mRequestingClass; + + /** + * The class name for {@code R.java}. + */ + private static final ClassName R_CLASS_NAME = ClassName.get("android", "R"); + + /** + * The class name of {@link android.content.res.ResourceId}. + */ + private static final ClassName RESOURCE_ID_CLASS_NAME = ClassName.get( + "android.content.res", "ResourceId"); + + /** + * The class name of {@link android.view.inspector.InspectionCompanion}. + */ + private static final ClassName INSPECTION_COMPANION = ClassName.get( + "android.view.inspector", "InspectionCompanion"); + + /** + * The class name of {@link android.view.inspector.PropertyMapper}. + */ + private static final ClassName PROPERTY_MAPPER = ClassName.get( + "android.view.inspector", "PropertyMapper"); + + /** + * The class name of {@link android.view.inspector.PropertyReader}. + */ + private static final ClassName PROPERTY_READER = ClassName.get( + "android.view.inspector", "PropertyReader"); + + /** + * The {@code mPropertiesMapped} field. + */ + private static final FieldSpec M_PROPERTIES_MAPPED = FieldSpec + .builder(TypeName.BOOLEAN, "mPropertiesMapped", Modifier.PRIVATE) + .initializer("false") + .addJavadoc( + "Set by {@link #mapProperties($T)} once properties have been mapped.\n", + PROPERTY_MAPPER) + .build(); + + /** + * The suffix of the generated class name after the class's binary name. + */ + private static final String GENERATED_CLASS_SUFFIX = "$$InspectionCompanion"; + + /** + * The null resource ID. + * + * @see android.content.res.ResourceId#ID_NULL + */ + private static final int NO_ID = 0; + + /** + * @param filer A filer to write the generated source to + * @param requestingClass A class object representing the class that invoked the generator + */ + public InspectionCompanionGenerator(Filer filer, Class requestingClass) { + mFiler = filer; + mRequestingClass = requestingClass; + } + + /** + * Generate and write an inspection companion. + * + * @param model The model to generated + * @throws IOException From the Filer + */ + public void generate(InspectableClassModel model) throws IOException { + generateFile(model).writeTo(mFiler); + } + + /** + * Generate a {@link JavaFile} from a model. + * + * This is package-public for testing. + * + * @param model The model to generate from + * @return A generated file of an {@link android.view.inspector.InspectionCompanion} + */ + JavaFile generateFile(InspectableClassModel model) { + return JavaFile + .builder(model.getClassName().packageName(), generateTypeSpec(model)) + .indent(" ") + .build(); + } + + /** + * Generate a {@link TypeSpec} for the {@link android.view.inspector.InspectionCompanion} + * for the supplied model. + * + * @param model The model to generate from + * @return A TypeSpec of the inspection companion + */ + private TypeSpec generateTypeSpec(InspectableClassModel model) { + final List<PropertyIdField> propertyIdFields = generatePropertyIdFields(model); + + TypeSpec.Builder builder = TypeSpec + .classBuilder(generateClassName(model)) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addSuperinterface(ParameterizedTypeName.get( + INSPECTION_COMPANION, model.getClassName())) + .addJavadoc("Inspection companion for {@link $T}.\n\n", model.getClassName()) + .addJavadoc("Generated by {@link $T}\n", getClass()) + .addJavadoc("on behalf of {@link $T}.\n", mRequestingClass) + .addField(M_PROPERTIES_MAPPED); + + for (PropertyIdField propertyIdField : propertyIdFields) { + builder.addField(propertyIdField.mFieldSpec); + } + + builder.addMethod(generateMapProperties(propertyIdFields)) + .addMethod(generateReadProperties(model, propertyIdFields)); + + generateGetNodeName(model).ifPresent(builder::addMethod); + + return builder.build(); + } + + /** + * Build a list of {@link PropertyIdField}'s for a model. + * + * To insure idempotency of the generated code, this method sorts the list of properties + * alphabetically by name. + * + * A {@link NameAllocator} is used to ensure that the field names are valid Java identifiers, + * and it prevents overlaps in names by suffixing them as needed. + * + * @param model The model to get properties from + * @return A list of properties and fields + */ + private List<PropertyIdField> generatePropertyIdFields(InspectableClassModel model) { + final NameAllocator nameAllocator = new NameAllocator(); + final List<Property> sortedProperties = new ArrayList<>(model.getAllProperties()); + final List<PropertyIdField> propertyIdFields = new ArrayList<>(sortedProperties.size()); + + sortedProperties.sort(Comparator.comparing(Property::getName)); + + for (Property property : sortedProperties) { + // Format a property to a member field name like "someProperty" -> "mSomePropertyId" + final String memberName = String.format( + "m%s%sId", + property.getName().substring(0, 1).toUpperCase(), + property.getName().substring(1)); + final FieldSpec fieldSpec = FieldSpec + .builder(TypeName.INT, nameAllocator.newName(memberName), Modifier.PRIVATE) + .addJavadoc("Property ID of {@code $L}.\n", property.getName()) + .build(); + + propertyIdFields.add(new PropertyIdField(fieldSpec, property)); + } + + return propertyIdFields; + } + + /** + * Generate a method definition for + * {@link android.view.inspector.InspectionCompanion#getNodeName()}, if needed. + * + * If {@link InspectableClassModel#getNodeName()} is empty, This method returns an empty + * optional, otherwise, it generates a simple method that returns the string value of the + * node name. + * + * @param model The model to generate from + * @return The method definition or an empty Optional + */ + private Optional<MethodSpec> generateGetNodeName(InspectableClassModel model) { + return model.getNodeName().map(nodeName -> MethodSpec.methodBuilder("getNodeName") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .returns(String.class) + .addStatement("return $S", nodeName) + .build()); + } + + /** + * Generate a method definition for + * {@link android.view.inspector.InspectionCompanion#mapProperties( + * android.view.inspector.PropertyMapper)}. + * + * @param propertyIdFields A list of properties to map to ID fields + * @return The method definition + */ + private MethodSpec generateMapProperties(List<PropertyIdField> propertyIdFields) { + final MethodSpec.Builder builder = MethodSpec.methodBuilder("mapProperties") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addParameter(PROPERTY_MAPPER, "propertyMapper"); + + propertyIdFields.forEach(p -> builder.addStatement(generatePropertyMapperInvocation(p))); + builder.addStatement("$N = true", M_PROPERTIES_MAPPED); + + return builder.build(); + } + + /** + * Generate a method definition for + * {@link android.view.inspector.InspectionCompanion#readProperties( + * Object, android.view.inspector.PropertyReader)}. + * + * @param model The model to generate from + * @param propertyIdFields A list of properties and ID fields to read from + * @return The method definition + */ + private MethodSpec generateReadProperties( + InspectableClassModel model, + List<PropertyIdField> propertyIdFields) { + final MethodSpec.Builder builder = MethodSpec.methodBuilder("readProperties") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addParameter(model.getClassName(), "inspectable") + .addParameter(PROPERTY_READER, "propertyReader") + .addCode(generatePropertyMapInitializationCheck()); + + for (PropertyIdField propertyIdField : propertyIdFields) { + builder.addStatement( + "propertyReader.read$L($N, inspectable.$L())", + methodSuffixForPropertyType(propertyIdField.mProperty.getType()), + propertyIdField.mFieldSpec, + propertyIdField.mProperty.getGetter()); + } + + return builder.build(); + } + + /** + * Generate a statement maps a property with a {@link android.view.inspector.PropertyMapper}. + * + * @param propertyIdField The property model and ID field to generate from + * @return A statement that invokes property mapper method + */ + private CodeBlock generatePropertyMapperInvocation(PropertyIdField propertyIdField) { + final CodeBlock.Builder builder = CodeBlock.builder(); + final Property property = propertyIdField.mProperty; + final FieldSpec fieldSpec = propertyIdField.mFieldSpec; + + builder.add( + "$N = propertyMapper.map$L($S,$W", + fieldSpec, + methodSuffixForPropertyType(property.getType()), + property.getName()); + + if (property.isAttributeIdInferrableFromR()) { + builder.add("$T.attr.$L", R_CLASS_NAME, property.getName()); + } else { + if (property.getAttributeId() == NO_ID) { + builder.add("$T.ID_NULL", RESOURCE_ID_CLASS_NAME); + } else { + builder.add("$L", String.format("0x%08x", property.getAttributeId())); + } + } + + switch (property.getType()) { + case INT_ENUM: + throw new RuntimeException("IntEnumMapping generation not implemented"); + case INT_FLAG: + throw new RuntimeException("IntFlagMapping generation not implemented"); + default: + builder.add(")"); + break; + } + + return builder.build(); + } + + /** + * Generate a check that throws + * {@link android.view.inspector.InspectionCompanion.UninitializedPropertyMapException} + * if the properties haven't been initialized. + * + * <pre> + * if (!mPropertiesMapped) { + * throw new InspectionCompanion.UninitializedPropertyMapException(); + * } + * </pre> + * + * @return A codeblock containing the property map initialization check + */ + private CodeBlock generatePropertyMapInitializationCheck() { + return CodeBlock.builder() + .beginControlFlow("if (!$N)", M_PROPERTIES_MAPPED) + .addStatement( + "throw new $T()", + INSPECTION_COMPANION.nestedClass("UninitializedPropertyMapException")) + .endControlFlow() + .build(); + } + + /** + * Generate the final class name for the inspection companion from the model's class name. + * + * The generated class is added to the same package as the source class. If the class in the + * model is a nested class, the nested class names are joined with {@code "$"}. The suffix + * {@code "$$InspectionCompanion"} is always added the the generated name. E.g.: For modeled + * class {@code com.example.Outer.Inner}, the generated class name will be + * {@code com.example.Outer$Inner$$InspectionCompanion}. + * + * @param model The model to generate from + * @return A class name for the generated inspection companion class + */ + private static ClassName generateClassName(InspectableClassModel model) { + final ClassName className = model.getClassName(); + + return ClassName.get( + className.packageName(), + String.join("$", className.simpleNames()) + GENERATED_CLASS_SUFFIX); + } + + /** + * Get the suffix for a {@code map} or {@code read} method for a property type. + * + * @param type The requested property type + * @return A method suffix + */ + private static String methodSuffixForPropertyType(Property.Type type) { + switch (type) { + case BOOLEAN: + return "Boolean"; + case BYTE: + return "Byte"; + case CHAR: + return "Char"; + case DOUBLE: + return "Double"; + case FLOAT: + return "Float"; + case INT: + return "Int"; + case LONG: + return "Long"; + case SHORT: + return "Short"; + case OBJECT: + return "Object"; + case COLOR: + return "Color"; + case GRAVITY: + return "Gravity"; + case INT_ENUM: + return "IntEnum"; + case INT_FLAG: + return "IntFlag"; + default: + throw new NoSuchElementException(String.format("No such property type, %s", type)); + } + } + + /** + * Value class that holds a {@link Property} and a {@link FieldSpec} for that property. + */ + private static final class PropertyIdField { + private final FieldSpec mFieldSpec; + private final Property mProperty; + + private PropertyIdField(FieldSpec fieldSpec, Property property) { + mFieldSpec = fieldSpec; + mProperty = property; + } + } +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/ModelProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/ModelProcessor.java new file mode 100644 index 000000000000..3ffcff8a87d3 --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/ModelProcessor.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 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.processor.view.inspector; + +import javax.lang.model.element.Element; + +/** + * An interface for annotation processors that operate on a single element and a class model. + */ +public interface ModelProcessor { + /** + * Process the supplied element, mutating the model as needed. + * + * @param element The annotated element to operate on + * @param model The model this element should be merged into + */ + void process(Element element, InspectableClassModel model); +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java new file mode 100644 index 000000000000..e531b67d9ea2 --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2018 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.processor.view.inspector; + +import static javax.tools.Diagnostic.Kind.ERROR; + +import com.squareup.javapoet.ClassName; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; + + +/** + * An annotation processor for the platform inspectable annotations. + * + * It mostly delegates to {@link ModelProcessor} and {@link InspectionCompanionGenerator}. This + * modular architecture allows the core generation code to be reused for comparable annotations + * outside the platform, such as in AndroidX. + * + * @see android.view.inspector.InspectableNodeName + * @see android.view.inspector.InspectableProperty + */ +@SupportedAnnotationTypes({ + PlatformInspectableProcessor.NODE_NAME_QUALIFIED_NAME +}) +public final class PlatformInspectableProcessor extends AbstractProcessor { + static final String NODE_NAME_QUALIFIED_NAME = + "android.view.inspector.InspectableNodeName"; + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + final Map<String, InspectableClassModel> modelMap = new HashMap<>(); + + for (TypeElement annotation : annotations) { + if (annotation.getQualifiedName().contentEquals(NODE_NAME_QUALIFIED_NAME)) { + runModelProcessor( + roundEnv.getElementsAnnotatedWith(annotation), + new InspectableNodeNameProcessor(NODE_NAME_QUALIFIED_NAME, processingEnv), + modelMap); + + + } else { + fail("Unexpected annotation type", annotation); + } + } + + final InspectionCompanionGenerator generator = + new InspectionCompanionGenerator(processingEnv.getFiler(), getClass()); + + for (InspectableClassModel model : modelMap.values()) { + try { + generator.generate(model); + } catch (IOException ioException) { + fail(String.format( + "Unable to generate inspection companion for %s due to %s", + model.getClassName().toString(), + ioException.getMessage())); + } + } + + return true; + } + + /** + * Run a {@link ModelProcessor} for a set of elements + * + * @param elements Elements to process, should be annotated correctly + * @param processor The processor to use + * @param modelMap A map of qualified class names to models + */ + private void runModelProcessor( + Set<? extends Element> elements, + ModelProcessor processor, + Map<String, InspectableClassModel> modelMap) { + for (Element element : elements) { + final Optional<TypeElement> classElement = enclosingClassElement(element); + + if (!classElement.isPresent()) { + fail("Element not contained in a class", element); + break; + } + + final InspectableClassModel model = modelMap.computeIfAbsent( + classElement.get().getQualifiedName().toString(), + k -> new InspectableClassModel(ClassName.get(classElement.get()))); + + processor.process(element, model); + } + } + + /** + * Get the nearest enclosing class if there is one. + * + * If {@param element} represents a class, it will be returned wrapped in an optional. + * + * @param element An element to search from + * @return A TypeElement of the nearest enclosing class or an empty optional + */ + private static Optional<TypeElement> enclosingClassElement(Element element) { + Element cursor = element; + + while (cursor != null) { + if (cursor.getKind() == ElementKind.CLASS) { + return Optional.of((TypeElement) cursor); + } + + cursor = cursor.getEnclosingElement(); + } + + return Optional.empty(); + } + + /** + * Print message and fail the build. + * + * @param message Message to print + */ + private void fail(String message) { + processingEnv.getMessager().printMessage(ERROR, message); + } + + /** + * Print message and fail the build. + * + * @param message Message to print + * @param element The element that failed + */ + private void fail(String message, Element element) { + processingEnv.getMessager().printMessage(ERROR, message, element); + } +} diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/ProcessingException.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/ProcessingException.java new file mode 100644 index 000000000000..6360e0a2de39 --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/ProcessingException.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 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.processor.view.inspector; + +import static javax.tools.Diagnostic.Kind.ERROR; + +import javax.annotation.processing.Messager; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; + +/** + * Internal exception used to signal an error processing an annotation. + */ +final class ProcessingException extends RuntimeException { + private final Element mElement; + private final AnnotationMirror mAnnotationMirror; + private final AnnotationValue mAnnotationValue; + + ProcessingException(String message) { + this(message, null, null, null); + } + + ProcessingException(String message, Element element) { + this(message, element, null, null); + } + + ProcessingException(String message, Element element, AnnotationMirror annotationMirror) { + this(message, element, annotationMirror, null); + } + + ProcessingException( + String message, + Element element, + AnnotationMirror annotationMirror, + AnnotationValue annotationValue) { + super(message); + mElement = element; + mAnnotationMirror = annotationMirror; + mAnnotationValue = annotationValue; + } + + /** + * Prints the exception to a Messager. + * + * @param messager A Messager to print to + */ + void print(Messager messager) { + if (mElement != null) { + if (mAnnotationMirror != null) { + if (mAnnotationValue != null) { + messager.printMessage( + ERROR, getMessage(), mElement, mAnnotationMirror, mAnnotationValue); + } else { + messager.printMessage(ERROR, getMessage(), mElement, mAnnotationMirror); + } + } else { + messager.printMessage(ERROR, getMessage(), mElement); + } + } else { + messager.printMessage(ERROR, getMessage()); + } + } +} diff --git a/tools/processors/view_inspector/src/resources/META-INF/services/javax.annotation.processing.Processor b/tools/processors/view_inspector/src/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 000000000000..fa4f71ffd0fa --- /dev/null +++ b/tools/processors/view_inspector/src/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +android.processor.inspector.view.PlatformInspectableProcessor diff --git a/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java new file mode 100644 index 000000000000..f639719800f6 --- /dev/null +++ b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2018 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.processor.view.inspector; + +import android.processor.view.inspector.InspectableClassModel.Property; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.TestCase.fail; + +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +import com.squareup.javapoet.ClassName; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.net.URL; +import java.util.Optional; + +/** + * Tests for {@link InspectionCompanionGenerator} + */ +public class InspectionCompanionGeneratorTest { + private static final String RESOURCE_PATH_TEMPLATE = + "android/processor/view/inspector/InspectionCompanionGeneratorTest/%s.java.txt"; + private static final ClassName TEST_CLASS_NAME = + ClassName.get("com.android.inspectable", "TestInspectable"); + private InspectableClassModel mModel; + private InspectionCompanionGenerator mGenerator; + + @Before + public void setup() { + mModel = new InspectableClassModel(TEST_CLASS_NAME); + mGenerator = new InspectionCompanionGenerator(null, getClass()); + } + + @Test + public void testNodeName() { + mModel.setNodeName(Optional.of("NodeName")); + assertGeneratedFileEquals("NodeName"); + } + + @Test + public void testNestedClass() { + mModel = new InspectableClassModel( + ClassName.get("com.android.inspectable", "Outer", "Inner")); + assertGeneratedFileEquals("NestedClass"); + } + + @Test + public void testSimpleProperties() { + addProperty("boolean", Property.Type.BOOLEAN, "getBoolean"); + addProperty("byte", Property.Type.BYTE, "getByte"); + addProperty("char", Property.Type.CHAR, "getChar"); + addProperty("double", Property.Type.DOUBLE, "getDouble"); + addProperty("float", Property.Type.FLOAT, "getFloat"); + addProperty("int", Property.Type.INT, "getInt"); + addProperty("long", Property.Type.LONG, "getLong"); + addProperty("short", Property.Type.SHORT, "getShort"); + + addProperty("object", Property.Type.OBJECT, "getObject"); + addProperty("color", Property.Type.COLOR, "getColor"); + addProperty("gravity", Property.Type.GRAVITY, "getGravity"); + + assertGeneratedFileEquals("SimpleProperties"); + } + + @Test + public void testNoAttributeId() { + final Property property = new Property("noAttributeProperty"); + property.setType(Property.Type.INT); + property.setGetter("getNoAttributeProperty"); + property.setAttributeIdInferrableFromR(false); + mModel.putProperty(property); + + assertGeneratedFileEquals("NoAttributeId"); + } + + @Test + public void testSuppliedAttributeId() { + final Property property = new Property("suppliedAttributeProperty"); + property.setType(Property.Type.INT); + property.setGetter("getSuppliedAttributeProperty"); + property.setAttributeId(0xdecafbad); + mModel.putProperty(property); + + assertGeneratedFileEquals("SuppliedAttributeId"); + } + + private Property addProperty(String name, Property.Type type, String getter) { + final Property property = new Property(name); + property.setType(type); + property.setGetter(getter); + mModel.putProperty(property); + return property; + } + + private void assertGeneratedFileEquals(String fileName) { + assertEquals( + loadTextResource(String.format(RESOURCE_PATH_TEMPLATE, fileName)), + mGenerator.generateFile(mModel).toString()); + } + + private String loadTextResource(String path) { + try { + final URL url = Resources.getResource(path); + assertNotNull(String.format("Resource file not found: %s", path), url); + return Resources.toString(url, Charsets.UTF_8); + } catch (IOException e) { + fail(e.getMessage()); + return null; + } + } +} diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt new file mode 100644 index 000000000000..2fc242c6cf4c --- /dev/null +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt @@ -0,0 +1,31 @@ +package com.android.inspectable; + +import android.view.inspector.InspectionCompanion; +import android.view.inspector.PropertyMapper; +import android.view.inspector.PropertyReader; +import java.lang.Override; + +/** + * Inspection companion for {@link Outer.Inner}. + * + * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} + * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. + */ +public final class Outer$Inner$$InspectionCompanion implements InspectionCompanion<Outer.Inner> { + /** + * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped. + */ + private boolean mPropertiesMapped = false; + + @Override + public void mapProperties(PropertyMapper propertyMapper) { + mPropertiesMapped = true; + } + + @Override + public void readProperties(Outer.Inner inspectable, PropertyReader propertyReader) { + if (!mPropertiesMapped) { + throw new InspectionCompanion.UninitializedPropertyMapException(); + } + } +} diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt new file mode 100644 index 000000000000..277e84065fb7 --- /dev/null +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt @@ -0,0 +1,39 @@ +package com.android.inspectable; + +import android.content.res.ResourceId; +import android.view.inspector.InspectionCompanion; +import android.view.inspector.PropertyMapper; +import android.view.inspector.PropertyReader; +import java.lang.Override; + +/** + * Inspection companion for {@link TestInspectable}. + * + * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} + * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. + */ +public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> { + /** + * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped. + */ + private boolean mPropertiesMapped = false; + + /** + * Property ID of {@code noAttributeProperty}. + */ + private int mNoAttributePropertyId; + + @Override + public void mapProperties(PropertyMapper propertyMapper) { + mNoAttributePropertyId = propertyMapper.mapInt("noAttributeProperty", ResourceId.ID_NULL); + mPropertiesMapped = true; + } + + @Override + public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) { + if (!mPropertiesMapped) { + throw new InspectionCompanion.UninitializedPropertyMapException(); + } + propertyReader.readInt(mNoAttributePropertyId, inspectable.getNoAttributeProperty()); + } +} diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt new file mode 100644 index 000000000000..11425482ce94 --- /dev/null +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt @@ -0,0 +1,37 @@ +package com.android.inspectable; + +import android.view.inspector.InspectionCompanion; +import android.view.inspector.PropertyMapper; +import android.view.inspector.PropertyReader; +import java.lang.Override; +import java.lang.String; + +/** + * Inspection companion for {@link TestInspectable}. + * + * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} + * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. + */ +public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> { + /** + * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped. + */ + private boolean mPropertiesMapped = false; + + @Override + public void mapProperties(PropertyMapper propertyMapper) { + mPropertiesMapped = true; + } + + @Override + public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) { + if (!mPropertiesMapped) { + throw new InspectionCompanion.UninitializedPropertyMapException(); + } + } + + @Override + public String getNodeName() { + return "NodeName"; + } +} diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt new file mode 100644 index 000000000000..57eb08041131 --- /dev/null +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt @@ -0,0 +1,109 @@ +package com.android.inspectable; + +import android.R; +import android.view.inspector.InspectionCompanion; +import android.view.inspector.PropertyMapper; +import android.view.inspector.PropertyReader; +import java.lang.Override; + +/** + * Inspection companion for {@link TestInspectable}. + * + * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} + * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. + */ +public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> { + /** + * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped. + */ + private boolean mPropertiesMapped = false; + + /** + * Property ID of {@code boolean}. + */ + private int mBooleanId; + + /** + * Property ID of {@code byte}. + */ + private int mByteId; + + /** + * Property ID of {@code char}. + */ + private int mCharId; + + /** + * Property ID of {@code color}. + */ + private int mColorId; + + /** + * Property ID of {@code double}. + */ + private int mDoubleId; + + /** + * Property ID of {@code float}. + */ + private int mFloatId; + + /** + * Property ID of {@code gravity}. + */ + private int mGravityId; + + /** + * Property ID of {@code int}. + */ + private int mIntId; + + /** + * Property ID of {@code long}. + */ + private int mLongId; + + /** + * Property ID of {@code object}. + */ + private int mObjectId; + + /** + * Property ID of {@code short}. + */ + private int mShortId; + + @Override + public void mapProperties(PropertyMapper propertyMapper) { + mBooleanId = propertyMapper.mapBoolean("boolean", R.attr.boolean); + mByteId = propertyMapper.mapByte("byte", R.attr.byte); + mCharId = propertyMapper.mapChar("char", R.attr.char); + mColorId = propertyMapper.mapColor("color", R.attr.color); + mDoubleId = propertyMapper.mapDouble("double", R.attr.double); + mFloatId = propertyMapper.mapFloat("float", R.attr.float); + mGravityId = propertyMapper.mapGravity("gravity", R.attr.gravity); + mIntId = propertyMapper.mapInt("int", R.attr.int); + mLongId = propertyMapper.mapLong("long", R.attr.long); + mObjectId = propertyMapper.mapObject("object", R.attr.object); + mShortId = propertyMapper.mapShort("short", R.attr.short); + mPropertiesMapped = true; + } + + @Override + public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) { + if (!mPropertiesMapped) { + throw new InspectionCompanion.UninitializedPropertyMapException(); + } + propertyReader.readBoolean(mBooleanId, inspectable.getBoolean()); + propertyReader.readByte(mByteId, inspectable.getByte()); + propertyReader.readChar(mCharId, inspectable.getChar()); + propertyReader.readColor(mColorId, inspectable.getColor()); + propertyReader.readDouble(mDoubleId, inspectable.getDouble()); + propertyReader.readFloat(mFloatId, inspectable.getFloat()); + propertyReader.readGravity(mGravityId, inspectable.getGravity()); + propertyReader.readInt(mIntId, inspectable.getInt()); + propertyReader.readLong(mLongId, inspectable.getLong()); + propertyReader.readObject(mObjectId, inspectable.getObject()); + propertyReader.readShort(mShortId, inspectable.getShort()); + } +} diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt new file mode 100644 index 000000000000..6b6ce2157481 --- /dev/null +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt @@ -0,0 +1,39 @@ +package com.android.inspectable; + +import android.view.inspector.InspectionCompanion; +import android.view.inspector.PropertyMapper; +import android.view.inspector.PropertyReader; +import java.lang.Override; + +/** + * Inspection companion for {@link TestInspectable}. + * + * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator} + * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}. + */ +public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> { + /** + * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped. + */ + private boolean mPropertiesMapped = false; + + /** + * Property ID of {@code suppliedAttributeProperty}. + */ + private int mSuppliedAttributePropertyId; + + @Override + public void mapProperties(PropertyMapper propertyMapper) { + mSuppliedAttributePropertyId = propertyMapper.mapInt("suppliedAttributeProperty", + 0xdecafbad); + mPropertiesMapped = true; + } + + @Override + public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) { + if (!mPropertiesMapped) { + throw new InspectionCompanion.UninitializedPropertyMapException(); + } + propertyReader.readInt(mSuppliedAttributePropertyId, inspectable.getSuppliedAttributeProperty()); + } +} diff --git a/tools/signedconfig/debug_key.pem b/tools/signedconfig/debug_key.pem new file mode 100644 index 000000000000..0af577bf81e1 --- /dev/null +++ b/tools/signedconfig/debug_key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIEfgtO+KPOoqJqTnqkDDKkAcOzyvtovsUO/ShLE6y4XRoAoGCCqGSM49 +AwEHoUQDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60pj1pnU8 +SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ== +-----END EC PRIVATE KEY----- diff --git a/tools/signedconfig/debug_public.pem b/tools/signedconfig/debug_public.pem new file mode 100644 index 000000000000..f61f81322b94 --- /dev/null +++ b/tools/signedconfig/debug_public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoE +CGbTEBTKKvdd2hO60pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ== +-----END PUBLIC KEY----- diff --git a/tools/signedconfig/debug_sign.sh b/tools/signedconfig/debug_sign.sh new file mode 100755 index 000000000000..28e54289f8f8 --- /dev/null +++ b/tools/signedconfig/debug_sign.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# Script to sign data with the debug keys. Outputs base64 for embedding into +# APK metadata. + +openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem $1 | base64 -w 0 +echo diff --git a/tools/signedconfig/gen_priv_key.sh b/tools/signedconfig/gen_priv_key.sh new file mode 100755 index 000000000000..834c86bc8c12 --- /dev/null +++ b/tools/signedconfig/gen_priv_key.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# This script acts as a record of how the debug key was generated. There should +# be no need to run it again. + +openssl ecparam -name prime256v1 -genkey -noout -out debug_key.pem +openssl ec -in debug_key.pem -pubout -out debug_public.pem diff --git a/tools/signedconfig/verify_b64.sh b/tools/signedconfig/verify_b64.sh new file mode 100755 index 000000000000..8e1f58ce7b45 --- /dev/null +++ b/tools/signedconfig/verify_b64.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Script to verify signatures, with both signature & data given in b64 +# Args: +# 1. data (base64 encoded) +# 2. signature (base64 encoded) +# The arg values can be taken from the debug log for SignedConfigService when verbose logging is +# enabled. + +openssl dgst -sha256 -verify $(dirname $0)/debug_public.pem -signature <(echo $2 | base64 -d) <(echo $1 | base64 -d) diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp index 88b7e2e9de21..5192a0e7bf19 100644 --- a/tools/stats_log_api_gen/main.cpp +++ b/tools/stats_log_api_gen/main.cpp @@ -264,6 +264,10 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, chainField.name.c_str(), chainField.name.c_str()); } } + } else if (*arg == JAVA_TYPE_BYTE_ARRAY) { + fprintf(out, ", %s arg%d, size_t arg%d_length", + cpp_type_name(*arg), argIndex, argIndex); + } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { fprintf(out, ", const std::map<int, int32_t>& arg%d_1, " "const std::map<int, int64_t>& arg%d_2, " @@ -343,6 +347,10 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, fprintf(out, " }\n"); fprintf(out, " event.end();\n\n"); + } else if (*arg == JAVA_TYPE_BYTE_ARRAY) { + fprintf(out, + " event.AppendCharArray(arg%d, arg%d_length);\n", + argIndex, argIndex); } else { if (*arg == JAVA_TYPE_STRING) { fprintf(out, " if (arg%d == NULL) {\n", argIndex); @@ -383,12 +391,17 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, chainField.name.c_str(), chainField.name.c_str()); } } + } else if (*arg == JAVA_TYPE_BYTE_ARRAY) { + fprintf(out, ", %s arg%d, size_t arg%d_length", + cpp_type_name(*arg), argIndex, argIndex); + } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { - fprintf(out, ", const std::map<int, int32_t>& arg%d_1, " - "const std::map<int, int64_t>& arg%d_2, " - "const std::map<int, char const*>& arg%d_3, " - "const std::map<int, float>& arg%d_4", - argIndex, argIndex, argIndex, argIndex); + fprintf(out, + ", const std::map<int, int32_t>& arg%d_1, " + "const std::map<int, int64_t>& arg%d_2, " + "const std::map<int, char const*>& arg%d_3, " + "const std::map<int, float>& arg%d_4", + argIndex, argIndex, argIndex, argIndex); } else { fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); } @@ -415,9 +428,11 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, chainField.name.c_str(), chainField.name.c_str()); } } - } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { - fprintf(out, ", arg%d_1, arg%d_2, arg%d_3, arg%d_4", - argIndex, argIndex, argIndex, argIndex); + } else if (*arg == JAVA_TYPE_BYTE_ARRAY) { + fprintf(out, ", arg%d, arg%d_length", argIndex, argIndex); + } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { + fprintf(out, ", arg%d_1, arg%d_2, arg%d_3, arg%d_4", argIndex, + argIndex, argIndex, argIndex); } else { fprintf(out, ", arg%d", argIndex); } @@ -580,6 +595,11 @@ static void write_cpp_usage( field->name.c_str(), field->name.c_str(), field->name.c_str()); + } else if (field->javaType == JAVA_TYPE_BYTE_ARRAY) { + fprintf(out, ", %s %s, size_t %s_length", + cpp_type_name(field->javaType), field->name.c_str(), + field->name.c_str()); + } else { fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str()); } @@ -613,6 +633,9 @@ static void write_cpp_method_header( "const std::map<int, char const*>& arg%d_3, " "const std::map<int, float>& arg%d_4", argIndex, argIndex, argIndex, argIndex); + } else if (*arg == JAVA_TYPE_BYTE_ARRAY) { + fprintf(out, ", %s arg%d, size_t arg%d_length", + cpp_type_name(*arg), argIndex, argIndex); } else { fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); } @@ -1128,6 +1151,7 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp hadStringOrChain = true; fprintf(out, " jbyte* jbyte_array%d;\n", argIndex); fprintf(out, " const char* str%d;\n", argIndex); + fprintf(out, " int str%d_length = 0;\n", argIndex); fprintf(out, " if (arg%d != NULL && env->GetArrayLength(arg%d) > " "0) {\n", @@ -1137,6 +1161,9 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp "env->GetByteArrayElements(arg%d, NULL);\n", argIndex, argIndex); fprintf(out, + " str%d_length = env->GetArrayLength(arg%d);\n", + argIndex, argIndex); + fprintf(out, " str%d = " "reinterpret_cast<char*>(env->GetByteArrayElements(arg%" "d, NULL));\n", @@ -1224,6 +1251,10 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp ? "str" : "arg"; fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex); + + if (*arg == JAVA_TYPE_BYTE_ARRAY) { + fprintf(out, ", %s%d_length", argName, argIndex); + } } argIndex++; } diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index c6acd026bd39..21d6b94fba24 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -193,5 +193,7 @@ interface IWifiManager int addNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); int removeNetworkSuggestions(in List<WifiNetworkSuggestion> networkSuggestions, in String packageName); + + String[] getFactoryMacAddresses(); } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index cad6d2997bfb..57c97eaf1f10 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -2062,7 +2062,6 @@ public class WifiManager { /** * @return true if this adapter supports Device-to-AP RTT */ - @SystemApi public boolean isDeviceToApRttSupported() { return isFeatureSupported(WIFI_FEATURE_D2AP_RTT); } @@ -4435,4 +4434,19 @@ public class WifiManager { public boolean isOweSupported() { return isFeatureSupported(WIFI_FEATURE_OWE); } + + /** + * Gets the factory Wi-Fi MAC addresses. + * @return Array of String representing Wi-Fi MAC addresses sorted lexically or an empty Array + * if failed. + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public String[] getFactoryMacAddresses() { + try { + return mService.getFactoryMacAddresses(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/AbstractWifiService.java index 0f4e3a8ba20f..36f66aa81661 100644 --- a/wifi/java/com/android/server/wifi/AbstractWifiService.java +++ b/wifi/java/com/android/server/wifi/AbstractWifiService.java @@ -452,4 +452,9 @@ public abstract class AbstractWifiService extends IWifiManager.Stub { List<WifiNetworkSuggestion> networkSuggestions, String callingPackageName) { throw new UnsupportedOperationException(); } + + @Override + public String[] getFactoryMacAddresses() { + throw new UnsupportedOperationException(); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 13c8c9ea7ead..1001b100cb3b 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -29,6 +29,7 @@ import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING; import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -88,6 +89,7 @@ public class WifiManagerTest { private static final int TEST_UID = 14553; private static final String TEST_PACKAGE_NAME = "TestPackage"; private static final String TEST_COUNTRY_CODE = "US"; + private static final String[] TEST_MAC_ADDRESSES = {"da:a1:19:0:0:0"}; @Mock Context mContext; @Mock @@ -1320,4 +1322,15 @@ i * Verify that a call to cancel WPS immediately returns a failure. assertEquals(WifiManager.NETWORK_SUGGESTIONS_MAX_PER_APP, mWifiManager.getMaxNumberOfNetworkSuggestionsPerApp()); } + + /** + * Verify getting the factory MAC address. + * @throws Exception + */ + @Test + public void testGetFactoryMacAddress() throws Exception { + when(mWifiService.getFactoryMacAddresses()).thenReturn(TEST_MAC_ADDRESSES); + assertArrayEquals(TEST_MAC_ADDRESSES, mWifiManager.getFactoryMacAddresses()); + verify(mWifiService).getFactoryMacAddresses(); + } } |