diff options
408 files changed, 16351 insertions, 5372 deletions
diff --git a/Android.bp b/Android.bp index 2ae7d6c1a0a4..4762cba7ecf9 100644 --- a/Android.bp +++ b/Android.bp @@ -265,6 +265,7 @@ 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", @@ -442,7 +443,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", @@ -451,6 +451,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", @@ -633,7 +634,6 @@ java_defaults { ":libupdate_engine_aidl", ":storaged_aidl", - ":netd_aidl", ":vold_aidl", ":installd_aidl", ":dumpstate_aidl", @@ -784,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 { @@ -1617,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 7a53327039e6..ed9ac402847a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -644,6 +644,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 @@ -6664,7 +6665,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); @@ -6845,6 +6846,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 @@ -9227,11 +9231,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 +9302,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 +9320,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 +9385,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 +9396,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 +9421,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 +10238,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"; @@ -11479,6 +11513,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 +11759,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; } @@ -22701,8 +22744,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 +22957,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 @@ -23353,6 +23397,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); @@ -23933,6 +23978,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 +24252,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 +24314,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 +24716,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 +24755,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"; @@ -28340,6 +28391,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); @@ -28548,6 +28600,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); @@ -28583,6 +28638,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); @@ -35999,6 +36055,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"; } @@ -36027,6 +36084,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 { @@ -36118,6 +36176,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"; @@ -37363,22 +37425,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"; @@ -37387,7 +37453,14 @@ 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_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 { @@ -37402,8 +37475,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 @@ -37434,6 +37509,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 @@ -37452,6 +37528,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); @@ -37476,6 +37553,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); @@ -37752,6 +37830,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; } @@ -40395,6 +40474,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 { @@ -40490,6 +40570,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(); @@ -40505,6 +40586,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 { @@ -42254,8 +42336,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 @@ -42826,6 +42909,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(); @@ -43713,10 +43810,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); @@ -44424,11 +44521,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 @@ -52411,6 +52510,7 @@ package android.view.textclassifier { 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(); @@ -52524,6 +52624,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(); @@ -52641,6 +52742,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); @@ -52672,6 +52774,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); @@ -52679,6 +52782,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(); @@ -52701,6 +52805,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; @@ -52735,6 +52840,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(); @@ -53235,7 +53341,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(); @@ -53282,7 +53388,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/system-current.txt b/api/system-current.txt index 542fdff8d0c3..eecbf217efca 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -7,6 +7,7 @@ package android { 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"; @@ -90,6 +91,7 @@ package android { 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"; @@ -298,6 +300,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 { @@ -314,7 +317,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); @@ -597,6 +599,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); @@ -614,7 +618,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 @@ -845,6 +854,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 { @@ -1031,6 +1042,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 { + 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(); @@ -1129,6 +1148,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[]); @@ -1241,6 +1264,7 @@ package android.content.pm { method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String); method public abstract int getIntentVerificationStatusAsUser(java.lang.String, int); method public abstract int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle); + method public java.lang.String getWellbeingPackageName(); method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); 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; @@ -1343,6 +1367,7 @@ package android.content.pm { field public static final int FLAG_REMOVED = 2; // 0x2 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; } @@ -1387,7 +1412,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(); @@ -2920,6 +2945,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); @@ -2940,7 +2972,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[]); } } @@ -3030,7 +3062,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); } @@ -4318,6 +4349,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(); @@ -4333,6 +4365,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"; @@ -4392,11 +4425,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); @@ -4507,6 +4560,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); @@ -4851,7 +4919,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"; @@ -5569,6 +5640,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); } @@ -5909,6 +5984,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(); diff --git a/api/test-current.txt b/api/test-current.txt index 46cbb52f6efa..22e800575da3 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -276,6 +276,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 +316,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); } @@ -353,6 +374,7 @@ package android.content.pm { public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { 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 +1005,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 +1093,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 +1158,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 +1280,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 { 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/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 392d40aaaaab..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; } /** @@ -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/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index 01c70286cff3..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 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 4c57d395315c..5e445d14a08b 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -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 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/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/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/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/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..81eac5a413a6 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -49,6 +49,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; @@ -79,6 +81,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 +1124,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. @@ -1974,6 +2035,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. @@ -9839,6 +9929,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 +9948,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 +9958,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 +10239,73 @@ 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; + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index fcf74ee301d8..1ff414619be1 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -415,7 +415,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 +428,7 @@ interface IDevicePolicyManager { List<String> getCrossProfileCalendarPackages(in ComponentName admin); boolean isPackageAllowedToAccessCalendarForUser(String packageName, int userHandle); List<String> getCrossProfileCalendarPackagesForUser(int userHandle); + + boolean isManagedKiosk(); + boolean isUnattendedManagedKiosk(); } 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/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java index 7cb245adb609..f3b2153faabb 100644 --- a/core/java/android/app/role/RoleManager.java +++ b/core/java/android/app/role/RoleManager.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.content.Intent; import android.os.Binder; @@ -212,6 +213,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()); } @@ -239,6 +241,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"); @@ -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) { @@ -312,6 +316,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) { @@ -350,6 +355,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"); 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/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/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/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/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b7df2bf5a5f7..da39b6394f8f 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. * @@ -6424,6 +6444,17 @@ public abstract class PackageManager { } /** + * @return the wellbeing app package name, or null if it's not defined by the OEM. + * + * @hide + */ + @SystemApi + 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..43c02228499e 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -53,6 +53,7 @@ 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; @IntDef(value = { PACKAGE_SYSTEM, PACKAGE_SETUP_WIZARD, @@ -61,6 +62,7 @@ public abstract class PackageManagerInternal { PACKAGE_BROWSER, PACKAGE_SYSTEM_TEXT_CLASSIFIER, PACKAGE_PERMISSION_CONTROLLER, + PACKAGE_WELLBEING, }) @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..20997d6c0d11 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -181,6 +181,17 @@ 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; + /** @hide */ @IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = { PROTECTION_FLAG_PRIVILEGED, @@ -197,6 +208,7 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { PROTECTION_FLAG_OEM, PROTECTION_FLAG_VENDOR_PRIVILEGED, PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER, + PROTECTION_FLAG_WELLBEING, }) @Retention(RetentionPolicy.SOURCE) public @interface ProtectionFlags {} @@ -386,6 +398,9 @@ 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"; + } 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/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/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 0bdfca7f5025..0c44a566b48a 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} operation 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/TransportInfo.java b/core/java/android/net/TransportInfo.java new file mode 100644 index 000000000000..b78d3feccfa0 --- /dev/null +++ b/core/java/android/net/TransportInfo.java @@ -0,0 +1,25 @@ +/* + * 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; + +/** + * 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/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/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/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/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/UserManager.java b/core/java/android/os/UserManager.java index 86f81d81a6c7..17ce79b83aa8 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -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..865b8f8482bd 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -16,13 +16,14 @@ package android.provider; - 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; @@ -694,6 +695,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 work 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 work 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 work 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 +1673,50 @@ public final class CalendarContract { Uri.parse("content://" + AUTHORITY + "/events"); /** + * The content:// style URL for querying Events table in the work 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 work 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 work 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 +1896,63 @@ public final class CalendarContract { Uri.parse("content://" + AUTHORITY + "/instances/searchbyday"); /** + * The content:// style URL for querying an instance range in the work 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 work profile, or cross profile calendar for the work profile is disabled in + * Settings, or this uri is queried from a package that is not whitelisted by + * profile owner of the work 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 work 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 work 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 work 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..ff772287de25 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,10 +240,19 @@ 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(ContentResolver, Uri) + */ + public static final String METADATA_TYPES = "android:documentMetadataTypes"; - /** {@hide} */ + /** + * Get Exif information using DocumentsContract#getDocumentMetadata. + * + * @see #getDocumentMetadata(ContentResolver, Uri) + */ public static final String METADATA_EXIF = "android:documentExif"; /** @@ -361,7 +375,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 +401,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 +439,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 +449,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 +459,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 +483,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 +511,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(ContentResolver, Uri) */ - public static final int FLAG_SUPPORTS_METADATA = 1 << 17; + public static final int FLAG_SUPPORTS_METADATA = 1 << 14; } /** @@ -663,7 +677,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 +693,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 +800,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 +1103,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 +1112,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 +1209,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,32 +1245,20 @@ 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); - } - /** * Create a new document with given MIME type and display name. * @@ -1260,49 +1267,55 @@ 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); - } - - /** {@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); } /** @@ -1318,64 +1331,46 @@ 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; - } - /** * Delete the given document. * * @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); - } - /** * Copies the given document. * @@ -1384,32 +1379,23 @@ 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); - } - /** * Moves the given document under a new parent. * @@ -1419,35 +1405,24 @@ 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); - } - /** * Removes the given document from a parent directory. * @@ -1458,68 +1433,50 @@ 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); - } - /** * Ejects the given root. It throws {@link IllegalStateException} when ejection failed. * * @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); - } - /** * 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 +1496,20 @@ 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; } /** @@ -1614,52 +1525,25 @@ 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); - } - - /** * Creates an intent for obtaining a web link for the specified document. * * <p>Note, that due to internal limitations, if there is already a web link @@ -1710,40 +1594,29 @@ 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); - } - - /** * Open the given image for thumbnail purposes, using any embedded EXIF * thumbnail if available, and providing orientation hints from the parent * image. @@ -1783,10 +1656,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 64aa088e26bd..9380695f39c4 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 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 ANGLE_ENABLED_APP = "angle_enabled_app"; + 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). * @@ -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, @@ -13926,7 +13989,8 @@ public final class Settings { } arg.putInt(CALL_METHOD_RESET_MODE_KEY, RESET_MODE_PACKAGE_DEFAULTS); 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/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/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java index 9e3aba416f9c..27df845ca3e0 100644 --- a/core/java/android/service/autofill/augmented/FillWindow.java +++ b/core/java/android/service/autofill/augmented/FillWindow.java @@ -26,6 +26,7 @@ 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; @@ -140,10 +141,24 @@ public final class FillWindow implements AutoCloseable { // 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; 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/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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index cb4788624935..4b9a2b98f154 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -200,8 +200,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. */ @@ -1052,7 +1050,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 @@ -3534,10 +3534,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 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/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index 1a7b91127a7b..04b94b0e03ad 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; @@ -654,6 +655,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 +669,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 +697,7 @@ public final class ConversationActions implements Parcelable { parcel.writeParcelable(mTypeConfig, flags); parcel.writeInt(mMaxSuggestions); parcel.writeStringList(mHints); + parcel.writeString(mCallingPackageName); } @Override @@ -694,7 +709,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 +745,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/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/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/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..b00e6fdd5e9a 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -25,7 +25,6 @@ import android.content.pm.PackageManager; import android.os.Build; import android.os.Environment; import android.os.Process; -import android.os.SystemProperties; import android.os.storage.StorageManager; import android.permission.PermissionManager.SplitPermissionInfo; import android.text.TextUtils; @@ -78,10 +77,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 +212,7 @@ public class SystemConfig { return mSplitPermissions; } - public ArrayMap<String, String> getSharedLibraries() { + public ArrayMap<String, SharedLibraryEntry> getSharedLibraries() { return mSharedLibraries; } @@ -497,6 +509,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 +518,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_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/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/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index 9620e4b276d7..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; @@ -417,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; @@ -635,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; @@ -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/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 594ae6b2f333..988eac0241cc 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1595,7 +1595,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 +1648,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 @@ -2171,6 +2171,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. --> @@ -4086,10 +4093,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. diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 089c59f3a09f..f8004ea78e6a 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -268,6 +268,9 @@ <!-- 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" /> </attr> <!-- Flags indicating more context for a permission group. --> @@ -1394,6 +1397,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 +2268,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 62ec5c474297..101f92b2097c 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3349,6 +3349,13 @@ 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. 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/symbols.xml b/core/res/res/values/symbols.xml index 82a679e14534..b24cdba5ec47 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3265,6 +3265,7 @@ <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_defaultWellbeingPackage" /> <java-symbol type="string" name="config_defaultContentCaptureService" /> <java-symbol type="string" name="config_defaultAugmentedAutofillService" /> 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 f1ed1c2952e9..3a37fb627b26 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -248,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, @@ -414,6 +415,7 @@ 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.SMS_ACCESS_RESTRICTION_ENABLED, @@ -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/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/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/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/input/Android.bp b/libs/input/Android.bp index f1d9397783ed..a2e8672d6d45 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -14,7 +14,7 @@ cc_library_shared { name: "libinputservice", - + cpp_std: "c++17", 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/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/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..242ae46f4d33 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -3308,6 +3308,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 +3374,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/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java index e6ad4441401a..9038f72f1476 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 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/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 5a9a7c8a62bd..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); } /** 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 d08717678dba..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,7 +105,8 @@ 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 zoneId, int groupId, int flags) { // TODO: Include zoneId into consideration. @@ -162,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); } @@ -440,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..6f2b6c9dafd4 100644 --- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java @@ -69,8 +69,11 @@ public class SmartActionsHelper { * 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; } @@ -86,8 +89,11 @@ public class SmartActionsHelper { getMostSalientActionText(entry.getNotification()), 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; } 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/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/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/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 ad884320ba0b..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, @@ -685,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); @@ -1004,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, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 00ea45c4e024..424368d2600c 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, null, makeDefault, requestingUserId, false); 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(requestingUserId, 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, requestingUserId, false) ? 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; } @@ -1061,36 +1078,47 @@ public class SettingsProvider extends ContentProvider { MUTATION_OPERATION_INSERT, forceNotify, 0); } - private void resetConfigSetting(int requestingUserId, int mode, String tag) { + private boolean deleteConfigSetting(String name, int requestingUserId, boolean forceNotify) { + if (DEBUG) { + Slog.v(LOG_TAG, "deleteConfigSetting(" + name + ", " + requestingUserId + + ", " + forceNotify + ")"); + } + return mutateConfigSetting(name, null, null, false, requestingUserId, + MUTATION_OPERATION_DELETE, forceNotify, 0); + } + + private void resetConfigSetting(int requestingUserId, int mode, String prefix) { if (DEBUG) { Slog.v(LOG_TAG, "resetConfigSetting(" + requestingUserId + ", " - + mode + ", " + tag + ")"); + + mode + ", " + prefix + ")"); } - mutateConfigSetting(null, null, tag, false, requestingUserId, + mutateConfigSetting(null, null, prefix, false, requestingUserId, MUTATION_OPERATION_RESET, false, mode); } - private boolean mutateConfigSetting(String name, String value, String tag, + private boolean mutateConfigSetting(String name, String value, String prefix, boolean makeDefault, int requestingUserId, int operation, boolean forceNotify, 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, + UserHandle.USER_SYSTEM, name, value, null, makeDefault, getCallingPackage(), forceNotify, null); } + case MUTATION_OPERATION_DELETE: { + return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_CONFIG, + UserHandle.USER_SYSTEM, name, forceNotify, 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 +1126,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 +2141,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); } @@ -2644,6 +2707,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 +2724,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 +2745,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 +2765,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 +2788,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/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..59de6a7e64b9 --- /dev/null +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.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 com.android.providers.settings; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; + +import static org.junit.Assert.assertNotNull; + +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_setUntrustedDefault() throws Exception { + String newValue = "value2"; + + // make sValue the untrusted default (set by root) + 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); + + executeShellCommand("device_config reset untrusted_defaults " + sNamespace); + result = getFromContentProvider(mContentResolver, sNamespace, sKey); + // back to the default + assertEquals(sValue, result); + + executeShellCommand("device_config reset trusted_defaults " + sNamespace); + result = getFromContentProvider(mContentResolver, sNamespace, sKey); + // not trusted default was set + assertNull(result); + } + + @Test + public void testReset_setTrustedDefault() throws Exception { + String newValue = "value2"; + + // make sValue the trusted default (set by system) + putWithContentProvider(mContentResolver, sNamespace, sKey, sValue, true); + // make newValue the current value + executeShellCommand( + "device_config put " + sNamespace + " " + sKey + " " + newValue); + String result = getFromContentProvider(mContentResolver, sNamespace, sKey); + assertEquals(newValue, result); + + executeShellCommand("device_config reset untrusted_defaults " + sNamespace); + result = getFromContentProvider(mContentResolver, sNamespace, sKey); + // back to the default + assertEquals(sValue, result); + + executeShellCommand("device_config reset trusted_defaults " + sNamespace); + result = getFromContentProvider(mContentResolver, sNamespace, sKey); + // our trusted default is still set + assertEquals(sValue, 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 e564711cfa00..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" /> 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/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/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/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/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/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java index 3f8583c6241b..d9fe98257d4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java @@ -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/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/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/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..9eb5737ad00d 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,17 @@ 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 = 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 = isEdgeSwipeAlongNavBar(touchDownH, mDragHPositive) + ? ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX : ACTION_SWIPE_RIGHT_INDEX; + tryToStartGesture(mGestureActions[index], true /* alignedWithNavBar */, + event); } } } @@ -333,6 +348,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 +506,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 +531,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..b351f1d9f150 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -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); 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..d99af1fb57bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -97,6 +97,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 +114,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); @@ -367,6 +370,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/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/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/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/QuickStepControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java index cdaa2420186f..abb8c7939e63 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); 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/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index 89220d5f836a..f2ae16129164 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -6609,6 +6609,24 @@ message MetricsEvent { // 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; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. 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/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index e8887e7a2ebe..612c9294f5d1 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -484,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. diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java index 35c51027df86..c562fb1b7dde 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java @@ -233,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"); 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/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index d76a5dff36d3..3a222311deab 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -1238,7 +1238,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); @@ -1394,6 +1402,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; @@ -1409,8 +1423,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. @@ -1497,7 +1509,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); } /** 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/core/Android.bp b/services/core/Android.bp index 784d398a2b3f..cccacf4bf837 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -15,14 +15,13 @@ java_library_static { "java/**/*.java", ":dumpstate_aidl", ":idmap2_aidl", - ":netd_aidl", - ":netd_metrics_aidl", ":installd_aidl", ":storaged_aidl", ":vold_aidl", ":mediaupdateservice_aidl", "java/com/android/server/EventLogTags.logtags", "java/com/android/server/am/EventLogTags.logtags", + ":netd_metrics_aidl", ], libs: [ 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/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/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/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..754e72e8731d 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()); } } @@ -17532,8 +17532,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 +17545,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 +17566,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 +17594,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 +17693,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) { 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/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/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/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/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 173f074e09a5..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,6 +78,7 @@ 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.util.ArrayUtils; @@ -179,6 +183,11 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ -310,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. @@ -1159,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); } @@ -1366,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! @@ -1789,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"); } } } @@ -2084,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); @@ -2196,7 +2246,7 @@ public class JobSchedulerService extends com.android.server.SystemService 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); @@ -3033,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)); @@ -3130,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); @@ -3163,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 "); @@ -3208,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); @@ -3283,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); @@ -3318,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/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..2d2f9e3ca84f 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); @@ -3684,7 +3681,7 @@ public class NotificationManagerService extends SystemService { verifyPrivilegedListener(token, user, false); createNotificationChannelGroup( pkg, getUidForPackageAndUser(pkg, user), group, false, true); - savePolicyFile(); + handleSavePolicyFile(); } @Override @@ -3733,7 +3730,7 @@ public class NotificationManagerService extends SystemService { } if (allow != mLockScreenAllowSecureNotifications) { mLockScreenAllowSecureNotifications = allow; - savePolicyFile(); + handleSavePolicyFile(); } } @@ -4492,7 +4489,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 +4620,7 @@ public class NotificationManagerService extends SystemService { mSnoozeHelper.snooze(r, mDuration); } r.recordSnoozed(); - savePolicyFile(); + handleSavePolicyFile(); } } @@ -4701,7 +4698,7 @@ public class NotificationManagerService extends SystemService { if (mReason != REASON_SNOOZED) { final boolean wasSnoozed = mSnoozeHelper.cancel(mUserId, mPkg, mTag, mId); if (wasSnoozed) { - savePolicyFile(); + handleSavePolicyFile(); } } } @@ -5511,7 +5508,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); } @@ -5745,9 +5747,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 +6264,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/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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index dca92ead1645..fcdb84fe2d30 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,7 @@ public class PackageManagerService extends IPackageManager.Stub final @Nullable String mSetupWizardPackage; final @Nullable String mStorageManagerPackage; final @Nullable String mSystemTextClassifierPackage; + final @Nullable String mWellbeingPackage; final @NonNull String mServicesSystemSharedLibraryPackageName; final @NonNull String mSharedSystemSharedLibraryPackageName; @@ -2093,28 +2090,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 +2197,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 +2780,8 @@ public class PackageManagerService extends IPackageManager.Stub mSystemTextClassifierPackage = getSystemTextClassifierPackageName(); + mWellbeingPackage = getWellbeingPackageName(); + // Now that we know all of the shared libraries, update all clients to have // the correct library paths. updateAllSharedLibrariesLPw(null); @@ -11258,7 +11250,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."); } @@ -12836,16 +12828,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 @@ -12853,15 +12846,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); @@ -13018,16 +13016,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"); @@ -13071,21 +13066,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)) { @@ -17928,7 +17925,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; @@ -17958,7 +17955,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 { @@ -17974,7 +17971,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; } @@ -18091,7 +18088,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); @@ -18116,6 +18113,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) { @@ -18302,6 +18307,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; @@ -19559,6 +19568,11 @@ public class PackageManagerService extends IPackageManager.Stub } @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) { if (!sUserManager.exists(userId)) return; @@ -22714,6 +22728,8 @@ public class PackageManagerService extends IPackageManager.Stub return mSystemTextClassifierPackage; case PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER: return mRequiredPermissionControllerPackage; + case PackageManagerInternal.PACKAGE_WELLBEING: + return mWellbeingPackage; } return null; } 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..59f67fff22fc 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,9 @@ public final class BasePermission { return (protectionLevel & PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER) != 0; } + public boolean isWellbeing() { + return (protectionLevel & PermissionInfo.PROTECTION_FLAG_WELLBEING) != 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..bc3c18d9d49c 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,12 @@ 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; + } } return allowed; } 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..2eea3a40eb62 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) { diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java index 8ce38385d47b..8711ddf58f25 100644 --- a/services/core/java/com/android/server/role/RoleManagerService.java +++ b/services/core/java/com/android/server/role/RoleManagerService.java @@ -156,21 +156,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 oldPackagesHash; - synchronized (mLock) { - oldPackagesHash = userState.getPackagesHashLocked(); - } + 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() { @@ -183,9 +178,7 @@ public class RoleManagerService extends SystemService { }); try { result.get(5, TimeUnit.SECONDS); - synchronized (mLock) { - userState.setPackagesHashLocked(packagesHash); - } + userState.setPackagesHash(packagesHash); } catch (InterruptedException | ExecutionException | TimeoutException e) { Slog.e(LOG_TAG, "Failed to grant defaults for user " + userId, e); } @@ -225,35 +218,38 @@ 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); + mUserStates.put(userId, userState); + } + return userState; } - 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 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; } - return controllerService; } private void onRemoveUser(@UserIdInt int userId) { + RoleUserState userState; synchronized (mLock) { mControllerServices.remove(userId); - RoleUserState userState = mUserStates.removeReturnOld(userId); - if (userState != null) { - userState.destroyLocked(); - } + userState = mUserStates.removeReturnOld(userId); + } + if (userState != null) { + userState.destroy(); } } @@ -264,10 +260,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 @@ -307,10 +301,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 @@ -327,7 +319,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 @@ -344,7 +336,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); } @@ -361,7 +353,7 @@ public class RoleManagerService extends SystemService { getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, "clearRoleHoldersAsUser"); - getControllerService(userId).onClearRoleHolders(roleName, callback); + getOrCreateControllerService(userId).onClearRoleHolders(roleName, callback); } @Override @@ -372,10 +364,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 @@ -388,10 +378,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 @@ -404,10 +392,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 @@ -440,16 +426,14 @@ public class RoleManagerService extends SystemService { dumpOutputStream = new DualDumpOutputStream(new IndentingPrintWriter(fout, " ")); } - synchronized (mLock) { - int[] userIds = mUserManagerInternal.getUserIds(); - int userIdsLength = userIds.length; - for (int i = 0; i < userIdsLength; i++) { - int userId = userIds[i]; + int[] userIds = mUserManagerInternal.getUserIds(); + int userIdsLength = userIds.length; + for (int i = 0; i < userIdsLength; i++) { + int userId = userIds[i]; - RoleUserState userState = getUserStateLocked(userId); - userState.dumpLocked(dumpOutputStream, "user_states", - RoleManagerServiceDumpProto.USER_STATES); - } + 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 327debf94437..ec614a451f54 100644 --- a/services/core/java/com/android/server/role/RoleUserState.java +++ b/services/core/java/com/android/server/role/RoleUserState.java @@ -62,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"; @@ -74,54 +73,51 @@ public class RoleUserState { @UserIdInt private final int mUserId; - @GuardedBy("RoleManagerService.mLock") + @NonNull + private final Object mLock = new Object(); + + @GuardedBy("mLock") private int mVersion = VERSION_UNDEFINED; - @GuardedBy("RoleManagerService.mLock") + @GuardedBy("mLock") @Nullable 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; - - readLocked(); - } - /** * 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 - * - * @return the new user state */ - @GuardedBy("RoleManagerService.mLock") - public static RoleUserState newInstanceLocked(@UserIdInt int userId) { - return new RoleUserState(userId); + public RoleUserState(@UserIdInt int userId) { + mUserId = userId; + + 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; + } } /** @@ -129,14 +125,15 @@ 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(); } /** @@ -144,9 +141,11 @@ public class RoleUserState { * * @return the hash representing the state of packages */ - @GuardedBy("RoleManagerService.mLock") - public String getPackagesHashLocked() { - return mPackagesHash; + @Nullable + public String getPackagesHash() { + synchronized (mLock) { + return mPackagesHash; + } } /** @@ -154,14 +153,15 @@ public class RoleUserState { * * @param packagesHash the hash representing the state of packages */ - @GuardedBy("RoleManagerService.mLock") - public void setPackagesHashLocked(@Nullable String packagesHash) { - throwIfDestroyedLocked(); - if (Objects.equals(mPackagesHash, packagesHash)) { - return; + public void setPackagesHash(@Nullable String packagesHash) { + synchronized (mLock) { + throwIfDestroyedLocked(); + if (Objects.equals(mPackagesHash, packagesHash)) { + return; + } + mPackagesHash = packagesHash; + scheduleWriteFileLocked(); } - mPackagesHash = packagesHash; - writeAsyncLocked(); } /** @@ -171,10 +171,11 @@ 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); + } } /** @@ -184,11 +185,12 @@ 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)); + } } /** @@ -196,33 +198,35 @@ 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(); } } @@ -236,20 +240,21 @@ 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; - } - boolean changed = roleHolders.add(packageName); - if (changed) { - writeAsyncLocked(); + public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) { + 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; + } + boolean changed = roleHolders.add(packageName); + if (changed) { + scheduleWriteFileLocked(); + } + return true; } - return true; } /** @@ -262,63 +267,54 @@ 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; - } - boolean changed = roleHolders.remove(packageName); - if (changed) { - writeAsyncLocked(); + public boolean removeRoleHolder(@NonNull String roleName, @NonNull String packageName) { + 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; + } + boolean changed = roleHolders.remove(packageName); + if (changed) { + scheduleWriteFileLocked(); + } + return true; } - return true; } /** * 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, mPackagesHash, 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 { @@ -385,18 +381,19 @@ public class RoleUserState { /** * Read the state from file. */ - @GuardedBy("RoleManagerService.mLock") - private void readLocked() { - 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); + } } } @@ -470,20 +467,28 @@ public class RoleUserState { * * @param dumpOutputStream the output stream to dump to */ - @GuardedBy("RoleManagerService.mLock") - public void dumpLocked(@NonNull DualDumpOutputStream dumpOutputStream, - @NonNull String fieldName, long fieldId) { - throwIfDestroyedLocked(); + 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, mVersion); - dumpOutputStream.write("packages_hash", RoleUserStateProto.PACKAGES_HASH, mPackagesHash); + dumpOutputStream.write("version", RoleUserStateProto.VERSION, version); + dumpOutputStream.write("packages_hash", RoleUserStateProto.PACKAGES_HASH, packagesHash); - int rolesSize = mRoles.size(); + int rolesSize = roles.size(); for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) { - String roleName = mRoles.keyAt(rolesIndex); - ArraySet<String> roleHolders = mRoles.valueAt(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); @@ -501,19 +506,33 @@ public class RoleUserState { dumpOutputStream.end(fieldToken); } + @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 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. */ - @GuardedBy("RoleManagerService.mLock") - public void destroyLocked() { - throwIfDestroyedLocked(); - mWriteHandler.removeCallbacksAndMessages(null); - getFile(mUserId).delete(); - mDestroyed = true; + public void destroy() { + synchronized (mLock) { + throwIfDestroyedLocked(); + mWriteHandler.removeCallbacksAndMessages(null); + getFile(mUserId).delete(); + mDestroyed = true; + } } - @GuardedBy("RoleManagerService.mLock") + @GuardedBy("mLock") private void throwIfDestroyedLocked() { if (mDestroyed) { throw new IllegalStateException("This RoleUserState has already been destroyed"); diff --git a/services/core/java/com/android/server/signedconfig/InvalidConfigException.java b/services/core/java/com/android/server/signedconfig/InvalidConfigException.java new file mode 100644 index 000000000000..f01baa4b0c5d --- /dev/null +++ b/services/core/java/com/android/server/signedconfig/InvalidConfigException.java @@ -0,0 +1,33 @@ +/* + * 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; + +/** + * 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/SignedConfig.java b/services/core/java/com/android/server/signedconfig/SignedConfig.java new file mode 100644 index 000000000000..e6bb800045c8 --- /dev/null +++ b/services/core/java/com/android/server/signedconfig/SignedConfig.java @@ -0,0 +1,171 @@ +/* + * 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. + */ +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 = "minSdk"; + private static final String CONFIG_KEY_MAX_SDK = "maxSdk"; + private static final String CONFIG_KEY_VALUES = "values"; + // TODO it may be better to use regular key/value pairs in a JSON object, rather than an array + // of objects with the 2 keys below. + private static final String CONFIG_KEY_KEY = "key"; + private static final String CONFIG_KEY_VALUE = "value"; + + /** + * 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); + JSONArray valueArray = json.getJSONArray(CONFIG_KEY_VALUES); + Map<String, String> values = new HashMap<>(); + for (int i = 0; i < valueArray.length(); ++i) { + JSONObject keyValuePair = valueArray.getJSONObject(i); + String key = keyValuePair.getString(CONFIG_KEY_KEY); + Object valueObject = keyValuePair.has(CONFIG_KEY_VALUE) + ? keyValuePair.get(CONFIG_KEY_VALUE) + : null; + 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..e4d799a2e3b7 100644 --- a/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java +++ b/services/core/java/com/android/server/signedconfig/SignedConfigApplicator.java @@ -17,12 +17,112 @@ 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.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; + + SignedConfigApplicator(Context context, String sourcePackage) { + mContext = context; + mSourcePackage = sourcePackage; } + private boolean checkSignature(String data, String signature) { + Slog.w(TAG, "SIGNATURE CHECK NOT IMPLEMENTED YET!"); + 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..be1d41dd392b 100644 --- a/services/core/java/com/android/server/signedconfig/SignedConfigService.java +++ b/services/core/java/com/android/server/signedconfig/SignedConfigService.java @@ -85,7 +85,8 @@ public class SignedConfigService { 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 da997baeebbe..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,7 +178,10 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } updateBounds(); - mWindowContainerController.onDisplayChanged(); + if (mDisplayContent != null) { + mDisplayContent.updateDisplayInfo(); + mService.mWindowManager.requestTraversal(); + } } @Override @@ -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); @@ -958,17 +961,23 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> 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.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); } @@ -1099,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); @@ -1122,7 +1131,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS */ boolean supportsSystemDecorations() { - return mWindowContainerController.supportsSystemDecorations(); + return mDisplayContent.supportsSystemDecorations(); } @VisibleForTesting @@ -1136,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*/); + } } /** @@ -1284,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 26a4cef06327..4e9c5ab39ea8 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2942,7 +2942,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 c41a173727b8..aca9702a45c8 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -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; @@ -609,8 +609,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } 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) { @@ -1797,7 +1797,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; } @@ -2714,16 +2714,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); } @@ -2733,9 +2733,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); @@ -2745,9 +2745,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); } } @@ -2869,8 +2869,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 " @@ -3072,11 +3071,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; @@ -3095,7 +3094,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; @@ -3724,7 +3723,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; @@ -3890,7 +3889,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); @@ -3945,10 +3944,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); } @@ -4692,7 +4691,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) { @@ -4761,8 +4760,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); } @@ -4844,8 +4842,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()) { @@ -5170,7 +5167,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"); @@ -5508,7 +5505,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } void executeAppTransition(ActivityOptions options) { - getDisplay().getWindowContainerController().executeAppTransition(); + getDisplay().mDisplayContent.executeAppTransition(); ActivityOptions.abort(options); } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index d92a9f2167d7..e761ad86c770 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(); } 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..182d1a0f9c5d 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(); } } @@ -5770,17 +5769,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 +6270,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/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/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 c7eadf9aa6eb..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 onRequestedOverrideConfigurationChanged(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 942218244505..42cd8e864322 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -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? diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index f7877c0a37c8..c5b42f99fcda 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -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) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 3a5c57841ea7..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.getRequestedOverrideConfiguration()); - } - } - boolean isLayoutNeeded() { final int numDisplays = mChildren.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { @@ -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/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..240b8206baf6 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 @@ -125,4 +126,14 @@ 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; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f68f4d7424f4..bc550dc8bd12 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -59,6 +59,8 @@ 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,6 +111,7 @@ 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.DevicePolicyManagerInternal; import android.app.admin.NetworkEvent; @@ -195,6 +198,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 +209,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; @@ -445,6 +448,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 +4060,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 +4179,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 +4411,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 +4439,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 +4470,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 +4501,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 +4532,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 +4563,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 @@ -4890,14 +4940,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 +5036,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 +5045,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 +5146,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(ident); } - mInjector.getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin( + getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin( UserHandle.USER_SYSTEM, timeMs); } @@ -5113,7 +5165,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } policy.mLastMaximumTimeToLock = timeMs; - mInjector.getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin( + getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin( userId, policy.mLastMaximumTimeToLock); } @@ -5285,6 +5337,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(ident); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.LOCK_NOW) + .setInt(flags) + .write(); } @Override @@ -5363,6 +5419,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 +5484,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); @@ -6116,6 +6184,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( @@ -7220,6 +7293,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 +9545,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 +10324,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 +10356,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(id); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_SECURE_SETTING) + .setAdmin(who) + .setStrings(setting, value) + .write(); } @Override @@ -10623,6 +10719,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { updateMaximumTimeToLockLocked(userId); } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SEPARATE_PROFILE_CHALLENGE_CHANGED) + .setBoolean(isSeparateProfileChallengeEnabled(userId)) + .write(); } @Override @@ -10963,6 +11063,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 +11121,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 +11129,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 +11963,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mSecurityLogMonitor.stop(); } } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_SECURITY_LOGGING_ENABLED) + .setAdmin(admin) + .setBoolean(enabled) + .write(); } @Override @@ -11885,13 +12005,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 +12042,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; } @@ -13278,32 +13406,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 +13583,79 @@ 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(); + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 2baef6af0c11..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; @@ -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); 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..c3a0ddaff85f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -4163,7 +4163,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)); 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/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..70fadd101a91 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java @@ -0,0 +1,417 @@ +/* + * 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("{\"minSdk\":2, \"maxSdk\": 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("{\"maxSdk\": 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("{\"minSdk\": 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("{\"minSdk\": 1, \"maxSdk\": 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("{\"minSdk\":null, \"maxSdk\": 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("{\"minSdk\":1, \"maxSdk\": 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("{\"minSdk\": 1, \"maxSdk\": 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("{\"minSdk\": 1, \"maxSdk\": 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( + "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"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( + "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": 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( + "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}, " + + "{\"key\":\"c\", \"value\": \"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( + "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"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( + "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"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( + "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"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( + "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}," + + "{\"key\":\"b\", \"value\": \"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( + "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"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( + "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": null}]}"); + SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a"), + mapOf("a", mapOf(null, "allitnil"))); + assertThat(config.values).containsExactly("a", "allitnil"); + } + + @Test + public void testParsePerSdkConfigSingleKeyNoValue() + throws JSONException, InvalidConfigException { + JSONObject json = new JSONObject( + "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\"}]}"); + SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b"), + emptyMap()); + assertThat(config.values).containsExactly("a", null); + } + + @Test + public void testParsePerSdkConfigValuesInvalid() throws JSONException { + JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 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("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [1, 2]}"); + try { + SignedConfig.parsePerSdkConfig(json, emptySet(), emptyMap()); + fail("Expected InvalidConfigException or JSONException"); + } catch (JSONException | InvalidConfigException e) { + // expected + } + } + + @Test + public void testParsePerSdkConfigConfigEntryNull() throws JSONException { + JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [null]}"); + 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\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}]}", + emptySet(), emptyMap()); + assertThat(config.perSdkConfig).hasSize(1); + } + + @Test + public void testParseSdkConfigMultiple() throws InvalidConfigException { + SignedConfig config = SignedConfig.parse( + "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}, " + + "{\"minSdk\": 2, \"maxSdk\": 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/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..95561110b707 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -238,6 +238,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { protected void reportUserInteraction(NotificationRecord r) { return; } + + @Override + protected void handleSavePolicyFile() { + return; + } } private class TestableToastCallback extends ITransientNotification.Stub { @@ -1809,8 +1814,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mListener = mock(ManagedServices.ManagedServiceInfo.class); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); - - try { +try { mBinderService.getNotificationChannelGroupsFromPrivilegedListener( null, PKG, Process.myUserHandle()); fail("listeners that don't have a companion device shouldn't be able to call this"); 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/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 3a56419f67ae..f553c35071a4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -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); @@ -598,10 +601,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 +617,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/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/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/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..65e18354a5ae 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); } /** 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 4394f9abbbfe..5c3368bd25ac 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -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/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/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 0a60e3409c9d..60eb18cacb88 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -78,6 +78,15 @@ 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. + * @hide + */ + 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. */ @@ -2392,6 +2401,7 @@ public class CarrierConfigManager { 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); 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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index fa9b76de2e6b..422e66de7e0b 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 @@ -2328,6 +2329,7 @@ public class TelephonyManager { * @see #NETWORK_TYPE_LTE * @see #NETWORK_TYPE_EHRPD * @see #NETWORK_TYPE_HSPAP + * @see #NETWORK_TYPE_NR * * @hide */ @@ -2379,6 +2381,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 +2568,8 @@ public class TelephonyManager { return "IWLAN"; case NETWORK_TYPE_LTE_CA: return "LTE_CA"; + case NETWORK_TYPE_NR: + return "NR"; default: return "UNKNOWN"; } @@ -9449,8 +9454,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 +9469,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 +9492,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 +9519,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 +9568,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/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/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/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 399dc5255176..88b9302afacb 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1716,7 +1716,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/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..50aef1d24faf 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,62 @@ 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 NetworkSpecifier on a NetworkCapability with multiple transports!"); + } 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(); + nc2.setTransportInfo(new TransportInfo() { + // empty + }); + + try { + nc1.combineCapabilities(nc2); + fail("Should not be able to combine NetworkCaabilities which contain TransportInfos"); + } catch (IllegalStateException expected) { + // empty + } + } + 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/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/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/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..f0b0ff6b979f --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java @@ -0,0 +1,51 @@ +/* + * 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.Optional; + +/** + * Model of an inspectable class derived from annotations. + * + * This class does not use any {javax.lang.model} objects to facilitate building models for testing + * {@link InspectionCompanionGenerator}. + */ +public final class InspectableClassModel { + private final ClassName mClassName; + private Optional<String> mNodeName = Optional.empty(); + + /** + * @param className The name of the modeled class + */ + public InspectableClassModel(ClassName className) { + mClassName = className; + } + + public ClassName getClassName() { + return mClassName; + } + + public Optional<String> getNodeName() { + return mNodeName; + } + + public void setNodeName(Optional<String> nodeName) { + mNodeName = nodeName; + } +} 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..fe0153d7f9af --- /dev/null +++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.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 android.processor.view.inspector; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeSpec; + +import java.io.IOException; +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; + + /** + * @param filer A filer to write the generated source to + * @param requestingClass A class object representing the class that invoked the generator + */ + public InspectionCompanionGenerator(final Filer filer, final 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) { + TypeSpec.Builder builder = TypeSpec + .classBuilder(generateClassName(model)) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addSuperinterface(ParameterizedTypeName.get( + ClassName.get("android.view.inspector", "InspectionCompanion"), + 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) + .addMethod(generateMapProperties(model)) + .addMethod(generateReadProperties(model)); + + generateGetNodeName(model).ifPresent(builder::addMethod); + + return builder.build(); + } + + /** + * 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)}. + * + * TODO: implement + * + * @param model The model to generate from + * @return The method definition + */ + private MethodSpec generateMapProperties(InspectableClassModel model) { + final ClassName propertyMapper = ClassName.get( + "android.view.inspector", "PropertyMapper"); + + return MethodSpec.methodBuilder("mapProperties") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addParameter(propertyMapper, "propertyMapper") + // TODO: add method body + .build(); + } + + /** + * Generate a method definition for + * {@link android.view.inspector.InspectionCompanion#readProperties( + * Object, android.view.inspector.PropertyReader)}. + * + * TODO: implement + * + * @param model The model to generate from + * @return The method definition + */ + private MethodSpec generateReadProperties(InspectableClassModel model) { + final ClassName propertyReader = ClassName.get( + "android.view.inspector", "PropertyReader"); + + return MethodSpec.methodBuilder("readProperties") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addParameter(model.getClassName(), "inspectable") + .addParameter(propertyReader, "propertyReader") + // TODO: add method body + .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 {"$"}. The suffix + * {"$$InspectionCompanion"} is always added the the generated name. E.g.: For modeled class + * {com.example.Outer.Inner}, the generated class name will be + * {com.example.Outer$Inner$$InspectionCompanion}. + * + * @param model The model to generate from + * @return A class name for the generated inspection companion class + */ + private ClassName generateClassName(final InspectableClassModel model) { + final ClassName className = model.getClassName(); + + return ClassName.get( + className.packageName(), + String.join("$", className.simpleNames()) + "$$InspectionCompanion"); + } +} 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..c02b0bdba1cf --- /dev/null +++ b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java @@ -0,0 +1,80 @@ +/* + * 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 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"); + } + + 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..e5fb6a2129f5 --- /dev/null +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt @@ -0,0 +1,22 @@ +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> { + @Override + public void mapProperties(PropertyMapper propertyMapper) { + } + + @Override + public void readProperties(Outer.Inner inspectable, PropertyReader propertyReader) { + } +} 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..a334f50bbdf5 --- /dev/null +++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt @@ -0,0 +1,28 @@ +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> { + @Override + public void mapProperties(PropertyMapper propertyMapper) { + } + + @Override + public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) { + } + + @Override + public String getNodeName() { + return "NodeName"; + } +} 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++; } |